github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/routing/routing_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package linuxrouting 5 6 import ( 7 "net" 8 "net/netip" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 "github.com/vishvananda/netlink" 13 14 "github.com/cilium/cilium/pkg/datapath/linux/linux_defaults" 15 "github.com/cilium/cilium/pkg/datapath/linux/route" 16 ipamOption "github.com/cilium/cilium/pkg/ipam/option" 17 "github.com/cilium/cilium/pkg/mac" 18 "github.com/cilium/cilium/pkg/node" 19 "github.com/cilium/cilium/pkg/option" 20 "github.com/cilium/cilium/pkg/testutils" 21 "github.com/cilium/cilium/pkg/testutils/netns" 22 ) 23 24 func setupLinuxRoutingSuite(tb testing.TB) { 25 testutils.PrivilegedTest(tb) 26 } 27 28 func TestConfigure(t *testing.T) { 29 setupLinuxRoutingSuite(t) 30 31 ns1 := netns.NewNetNS(t) 32 ns1.Do(func() error { 33 ip, ri := getFakes(t, true) 34 masterMAC := ri.MasterIfMAC 35 ifaceCleanup := createDummyDevice(t, masterMAC) 36 defer ifaceCleanup() 37 38 runConfigureThenDelete(t, ri, ip, 1500) 39 return nil 40 }) 41 42 ns2 := netns.NewNetNS(t) 43 ns2.Do(func() error { 44 ip, ri := getFakes(t, false) 45 masterMAC := ri.MasterIfMAC 46 ifaceCleanup := createDummyDevice(t, masterMAC) 47 defer ifaceCleanup() 48 49 runConfigureThenDelete(t, ri, ip, 1500) 50 return nil 51 }) 52 } 53 54 func TestConfigureRouteWithIncompatibleIP(t *testing.T) { 55 setupLinuxRoutingSuite(t) 56 57 _, ri := getFakes(t, true) 58 ipv6 := netip.MustParseAddr("fd00::2").AsSlice() 59 err := ri.Configure(ipv6, 1500, false, false) 60 require.Error(t, err) 61 require.ErrorContains(t, err, "IP not compatible") 62 } 63 64 func TestDeleteRouteWithIncompatibleIP(t *testing.T) { 65 setupLinuxRoutingSuite(t) 66 67 ipv6 := netip.MustParseAddr("fd00::2") 68 err := Delete(ipv6, false) 69 require.Error(t, err) 70 require.ErrorContains(t, err, "IP not compatible") 71 } 72 73 func TestDelete(t *testing.T) { 74 setupLinuxRoutingSuite(t) 75 76 fakeIP, fakeRoutingInfo := getFakes(t, true) 77 masterMAC := fakeRoutingInfo.MasterIfMAC 78 79 tests := []struct { 80 name string 81 preRun func() netip.Addr 82 wantErr bool 83 }{ 84 { 85 name: "valid IP addr matching rules", 86 preRun: func() netip.Addr { 87 runConfigure(t, fakeRoutingInfo, fakeIP, 1500) 88 return fakeIP 89 }, 90 wantErr: false, 91 }, 92 { 93 name: "IP addr doesn't match rules", 94 preRun: func() netip.Addr { 95 ip := netip.MustParseAddr("192.168.2.233") 96 97 runConfigure(t, fakeRoutingInfo, fakeIP, 1500) 98 return ip 99 }, 100 wantErr: true, 101 }, 102 { 103 name: "IP addr matches more than number expected", 104 preRun: func() netip.Addr { 105 ip := netip.MustParseAddr("192.168.2.233") 106 107 runConfigure(t, fakeRoutingInfo, ip, 1500) 108 109 // Find interface ingress rules so that we can create a 110 // near-duplicate. 111 rules, err := route.ListRules(netlink.FAMILY_V4, &route.Rule{ 112 Priority: linux_defaults.RulePriorityIngress, 113 }) 114 require.Nil(t, err) 115 require.NotEqual(t, 0, len(rules)) 116 117 // Insert almost duplicate rule; the reason for this is to 118 // trigger an error while trying to delete the ingress rule. We 119 // are setting the Src because ingress rules don't have 120 // one (only Dst), thus we set Src to create a near-duplicate. 121 r := rules[0] 122 r.Src = &net.IPNet{IP: fakeIP.AsSlice(), Mask: net.CIDRMask(32, 32)} 123 require.Nil(t, netlink.RuleAdd(&r)) 124 125 return ip 126 }, 127 wantErr: true, 128 }, 129 { 130 name: "fails to delete rules due to masquerade misconfiguration", 131 preRun: func() netip.Addr { 132 runConfigure(t, fakeRoutingInfo, fakeIP, 1500) 133 // inconsistency with fakeRoutingInfo.Masquerade should lead to failure 134 option.Config.EnableIPv4Masquerade = false 135 return fakeIP 136 }, 137 wantErr: true, 138 }, 139 } 140 for _, tt := range tests { 141 t.Log("Test: " + tt.name) 142 ns := netns.NewNetNS(t) 143 ns.Do(func() error { 144 ifaceCleanup := createDummyDevice(t, masterMAC) 145 defer ifaceCleanup() 146 147 ip := tt.preRun() 148 err := Delete(ip, false) 149 require.Equal(t, tt.wantErr, (err != nil)) 150 return nil 151 }) 152 } 153 } 154 155 func runConfigureThenDelete(t *testing.T, ri RoutingInfo, ip netip.Addr, mtu int) { 156 // Create rules and routes 157 beforeCreationRules, beforeCreationRoutes := listRulesAndRoutes(t, netlink.FAMILY_V4) 158 runConfigure(t, ri, ip, mtu) 159 afterCreationRules, afterCreationRoutes := listRulesAndRoutes(t, netlink.FAMILY_V4) 160 161 require.NotEqual(t, 0, len(afterCreationRules)) 162 require.NotEqual(t, 0, len(afterCreationRoutes)) 163 require.NotEqual(t, len(afterCreationRules), len(beforeCreationRules)) 164 require.NotEqual(t, len(afterCreationRoutes), len(beforeCreationRoutes)) 165 166 // Delete rules and routes 167 beforeDeletionRules, beforeDeletionRoutes := listRulesAndRoutes(t, netlink.FAMILY_V4) 168 runDelete(t, ip) 169 afterDeletionRules, afterDeletionRoutes := listRulesAndRoutes(t, netlink.FAMILY_V4) 170 171 require.NotEqual(t, len(afterDeletionRules), len(beforeDeletionRules)) 172 require.NotEqual(t, len(afterDeletionRoutes), len(beforeDeletionRoutes)) 173 require.Equal(t, len(beforeCreationRules), len(afterDeletionRules)) 174 require.Equal(t, len(beforeCreationRoutes), len(afterDeletionRoutes)) 175 } 176 177 func runConfigure(t *testing.T, ri RoutingInfo, ip netip.Addr, mtu int) { 178 err := ri.Configure(ip.AsSlice(), mtu, false, false) 179 require.Nil(t, err) 180 } 181 182 func runDelete(t *testing.T, ip netip.Addr) { 183 err := Delete(ip, false) 184 require.Nil(t, err) 185 } 186 187 // listRulesAndRoutes returns all rules and routes configured on the machine 188 // this test is running on. Note that this function is intended to be used 189 // within a network namespace for isolation. 190 func listRulesAndRoutes(t *testing.T, family int) ([]netlink.Rule, []netlink.Route) { 191 rules, err := route.ListRules(family, nil) 192 require.Nil(t, err) 193 194 // Rules are created under specific tables, so find the routes that are in 195 // those tables. 196 var routes []netlink.Route 197 for _, r := range rules { 198 rr, err := netlink.RouteListFiltered(family, &netlink.Route{ 199 Table: r.Table, 200 }, netlink.RT_FILTER_TABLE) 201 require.Nil(t, err) 202 203 routes = append(routes, rr...) 204 } 205 206 return rules, routes 207 } 208 209 // createDummyDevice creates a new dummy device with a MAC of `macAddr` to be 210 // used as a harness in this test. This function returns a function which can 211 // be used to remove the device for cleanup purposes. 212 func createDummyDevice(t *testing.T, macAddr mac.MAC) func() { 213 if linkExistsWithMAC(t, macAddr) { 214 t.FailNow() 215 } 216 217 dummy := &netlink.Dummy{ 218 LinkAttrs: netlink.LinkAttrs{ 219 // NOTE: This name must be less than 16 chars, source: 220 // https://elixir.bootlin.com/linux/v5.6/source/include/uapi/linux/if.h#L33 221 Name: "linuxrout-test", 222 HardwareAddr: net.HardwareAddr(macAddr), 223 }, 224 } 225 err := netlink.LinkAdd(dummy) 226 require.Nil(t, err) 227 228 found := linkExistsWithMAC(t, macAddr) 229 require.Equal(t, true, found) 230 231 return func() { 232 require.Nil(t, netlink.LinkDel(dummy)) 233 } 234 } 235 236 // getFakes returns a fake IP simulating an Endpoint IP and RoutingInfo as test harnesses. 237 // To create routing info with a list of CIDRs which the interface has access to, set withCIDR parameter to true 238 func getFakes(t *testing.T, withCIDR bool) (netip.Addr, RoutingInfo) { 239 fakeGateway := netip.MustParseAddr("192.168.2.1") 240 fakeSubnet1CIDR := netip.MustParsePrefix("192.168.0.0/16") 241 fakeSubnet2CIDR := netip.MustParsePrefix("192.170.0.0/16") 242 fakeMAC, err := mac.ParseMAC("00:11:22:33:44:55") 243 require.Nil(t, err) 244 require.NotNil(t, fakeMAC) 245 246 var fakeRoutingInfo *RoutingInfo 247 if withCIDR { 248 fakeRoutingInfo, err = parse( 249 fakeGateway.String(), 250 []string{fakeSubnet1CIDR.String(), fakeSubnet2CIDR.String()}, 251 fakeMAC.String(), 252 "1", 253 ipamOption.IPAMENI, 254 true, 255 ) 256 } else { 257 fakeRoutingInfo, err = parse( 258 fakeGateway.String(), 259 nil, 260 fakeMAC.String(), 261 "1", 262 ipamOption.IPAMAzure, 263 false, 264 ) 265 } 266 require.Nil(t, err) 267 require.NotNil(t, fakeRoutingInfo) 268 269 node.SetRouterInfo(fakeRoutingInfo) 270 option.Config.IPAM = fakeRoutingInfo.IpamMode 271 option.Config.EnableIPv4Masquerade = fakeRoutingInfo.Masquerade 272 273 fakeIP := netip.MustParseAddr("192.168.2.123") 274 return fakeIP, *fakeRoutingInfo 275 } 276 277 func linkExistsWithMAC(t *testing.T, macAddr mac.MAC) bool { 278 links, err := netlink.LinkList() 279 require.Nil(t, err) 280 281 for _, link := range links { 282 if link.Attrs().HardwareAddr.String() == macAddr.String() { 283 return true 284 } 285 } 286 287 return false 288 }