Aaron U'Ren c3f90c54b3
Fix Misc DSR Issues (#1174)
* fact(NSC): consolidate constants to top

* fix(NSC): increase IPVS add service logging

* fix(NSC): improve logging for FWMark IPVS entries

* fix(NSC): add missing parameter to logging

* feat(NSC): generate unique FW marks

Because we trim the 32-bit FNV-1a hash to 16 bits there is the potential
for FW marks to collide with each other even for unique inputs of IP,
protocol, and port. This reduces that chance up to the 16-bit max by
keeping track of which FW marks we've already allocated and what IP,
protocol, port combo they've been allocated for.

Fixes #1045

* fact(NSC): move utility funcs to utils

* fix(NSC): reduce IPVS service shell outs

This also aligns it more with the almost identical function used for
non-FWmarked services ipvsAddService() which is also called from
setupExternalIPServices and passes in this same list of ipvsServices.

* fix(NSC): fix & consolidate DSR cleanup code

A lot of this is refactor work, but its important to know why the DSR
mangle tables were not being cleaned up in the first place. When we
transitioned to iptables-save to look over the mangle rules, we didn't
realize that iptables-save changes the format of the marks from integer
values (which is what the CLI works with) to hexadecimal.

This made it so that we were never actually matching on a mangle rule,
which left them all behind. When these mangle rules were left, it meant
that IPs that used to be part of a DSR service were essentially
black-holed on the system and were no longer route-able.

Fixes #1167

* doc(dsr): expand DSR documentation

fixes #1055

* ensure active service map is updated for non DSR services

Co-authored-by: Murali Reddy <muralimmreddy@gmail.com>
2021-10-14 16:14:05 +05:30

139 lines
6.3 KiB
Go

package proxy
import (
"fmt"
"net"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func getMoqNSC() *NetworkServicesController {
lnm := NewLinuxNetworkMock()
mockedLinuxNetworking := &LinuxNetworkingMock{
cleanupMangleTableRuleFunc: lnm.cleanupMangleTableRule,
getKubeDummyInterfaceFunc: lnm.getKubeDummyInterface,
ipAddrAddFunc: lnm.ipAddrAdd,
ipvsAddServerFunc: lnm.ipvsAddServer,
ipvsAddServiceFunc: lnm.ipvsAddService,
ipvsDelServiceFunc: lnm.ipvsDelService,
ipvsGetDestinationsFunc: lnm.ipvsGetDestinations,
ipvsGetServicesFunc: lnm.ipvsGetServices,
setupPolicyRoutingForDSRFunc: lnm.setupPolicyRoutingForDSR,
setupRoutesForExternalIPForDSRFunc: lnm.setupRoutesForExternalIPForDSR,
}
return &NetworkServicesController{
nodeIP: net.ParseIP("10.0.0.0"),
nodeHostName: "node-1",
ln: mockedLinuxNetworking,
fwMarkMap: map[uint32]string{},
}
}
func TestNetworkServicesController_generateUniqueFWMark(t *testing.T) {
t.Run("ensure same service protocol and port get same FW mark", func(t *testing.T) {
nsc := getMoqNSC()
fwMark1, err1 := nsc.generateUniqueFWMark("10.255.0.1", "TCP", "80")
fwMark2, err2 := nsc.generateUniqueFWMark("10.255.0.1", "TCP", "80")
assert.NoError(t, err1, "there shouldn't have been an error calling generateUniqueFWMark the first time")
assert.NoError(t, err2, "there shouldn't have been an error calling generateUniqueFWMark the second time")
assert.Equal(t, fwMark1, fwMark2,
"expected the FW marks for generateUniqueFWMark to be the same when called with the same parameters")
})
t.Run("ensure FW marks cannot be duplicated", func(t *testing.T) {
nsc := getMoqNSC()
fwMark1, err1 := nsc.generateUniqueFWMark("10.255.0.1", "TCP", "80")
// Now we change the port number of the backing map so that when generateUniqueFWMark evaluates the same service
// it will find that it doesn't match and give attempt to give us a new FW mark
nsc.fwMarkMap[fwMark1] = fmt.Sprintf("%s-%s-%s", "10.255.0.1", "TCP", "81")
fwMark2, err2 := nsc.generateUniqueFWMark("10.255.0.1", "TCP", "80")
assert.NoError(t, err1, "there shouldn't have been an error calling generateUniqueFWMark the first time")
assert.NoError(t, err2, "there shouldn't have been an error calling generateUniqueFWMark the second time")
assert.NotEqual(t, fwMark1, fwMark2,
"expected the FW marks for generateUniqueFWMark to be different when we changed the "+
"stored service key of the fwMarkMap")
})
t.Run("ensure error is passed when generateUniqueFWMark is filled", func(t *testing.T) {
nsc := getMoqNSC()
fwMark1, err1 := nsc.generateUniqueFWMark("10.255.0.1", "TCP", "80")
assert.NoError(t, err1, "there shouldn't have been an error calling generateUniqueFWMark the first time")
// Artificially fill up the fwMarkMap using the same way that the function internally would increment the
// service if it conflicted which is <ip>-<protocol>-<port>-<increment> up to maxUniqueFWMarkInc size
for i := 1; i < 16380+1; i++ {
_, err1 = nsc.generateUniqueFWMark("10.255.0.1", "TCP", fmt.Sprintf("80-%d", i))
assert.NoError(t, err1, "there shouldn't have been an error calling generateUniqueFWMark the %d time", i)
}
// Then we need to change the original FW mark so that it doesn't just return that one as a match
nsc.fwMarkMap[fwMark1] = fmt.Sprintf("%s-%s-%s", "10.255.0.1", "TCP", "81")
fwMark2, err2 := nsc.generateUniqueFWMark("10.255.0.1", "TCP", "80")
assert.EqualError(t, err2,
fmt.Sprintf("could not obtain a unique FWMark for %s:%s:%s after %d tries", "TCP", "10.255.0.1", "80", 16380),
"expected an error after filling up the internal fwMarkMap with possibilities")
assert.Equal(t, uint32(0), fwMark2, "excepted FW mark to be 0 after an error")
})
}
func TestNetworkServicesController_lookupFWMarkByService(t *testing.T) {
t.Run("ensure existing FW mark is found in lookup", func(t *testing.T) {
nsc := getMoqNSC()
fwMark1, err1 := nsc.generateUniqueFWMark("10.255.0.1", "TCP", "80")
fwMark2, err2 := nsc.lookupFWMarkByService("10.255.0.1", "TCP", "80")
assert.NoError(t, err1, "there shouldn't have been an error calling generateUniqueFWMark")
assert.NoError(t, err2, "there shouldn't have been an error calling lookupFWMarkByService")
assert.Equal(t, fwMark1, fwMark2,
"given the same inputs, lookupFWMarkByService should be able to find the previously generated FW mark")
})
t.Run("ensure error is returned when a service doesn't exist in FW mark map", func(t *testing.T) {
nsc := getMoqNSC()
fwMark, err := nsc.lookupFWMarkByService("10.255.0.1", "TCP", "80")
assert.EqualErrorf(t, err,
fmt.Sprintf("no key matching %s:%s:%s was found in fwMarkMap", "TCP", "10.255.0.1", "80"),
"expected to get an error when service had not yet been added to fwMarkMap")
assert.Equal(t, uint32(0), fwMark, "expected FW mark to be 0 on error condition")
})
}
func TestNetworkServicesController_lookupServiceByFWMark(t *testing.T) {
t.Run("ensure that the found service matches the same inputs that were passed in to create the FW mark", func(t *testing.T) {
ip := "10.255.0.1"
protocol := "TCP"
port := 80
nsc := getMoqNSC()
fwMark, err := nsc.generateUniqueFWMark(ip, protocol, strconv.Itoa(port))
assert.NoError(t, err, "there shouldn't have been an error calling generateUniqueFWMark")
foundIP, foundProtocol, foundPort, err1 := nsc.lookupServiceByFWMark(fwMark)
assert.NoError(t, err1, "there shouldn't have been an error calling lookupServiceByFWMark")
assert.Equal(t, ip, foundIP, "IP addresses should match given matching inputs")
assert.Equal(t, protocol, foundProtocol, "protocol should match given matching inputs")
assert.Equal(t, port, foundPort, "port should match given matching inputs")
})
t.Run("ensure error is returned if no service is found for FW mark", func(t *testing.T) {
nsc := getMoqNSC()
foundIP, foundProtocol, foundPort, err1 := nsc.lookupServiceByFWMark(uint32(1002))
assert.Errorf(t, err1, "could not find service matching the given FW mark",
"an error should be returned for a made-up FW mark")
assert.Empty(t, foundIP, "IP should be empty on error")
assert.Empty(t, foundProtocol, "protocol should be empty on error")
assert.Zero(t, foundPort, "port should be zero on error")
})
}