@@ -10,16 +10,33 @@ import (
1010 "os/exec"
1111 "runtime"
1212 "strconv"
13+ "strings"
1314 "time"
1415
15- "github.com/Microsoft/hcsshim/internal/guest/prot"
1616 "github.com/Microsoft/hcsshim/internal/log"
17+ "github.com/Microsoft/hcsshim/internal/protocol/guestresource"
1718 "github.com/pkg/errors"
1819 "github.com/sirupsen/logrus"
1920 "github.com/vishvananda/netlink"
2021 "github.com/vishvananda/netns"
2122)
2223
24+ var (
25+ // function definitions for mocking configureLink
26+ netlinkAddrAdd = netlink .AddrAdd
27+ netlinkRuleAdd = netlink .RuleAdd
28+ netlinkRouteAdd = netlink .RouteAdd
29+ )
30+
31+ const (
32+ ipv4GwDestination = "0.0.0.0/0"
33+ ipv4EmptyGw = "0.0.0.0"
34+ ipv6GwDestination = "::/0"
35+ ipv6EmptyGw = "::"
36+
37+ unreachableErr = "network is unreachable"
38+ )
39+
2340// MoveInterfaceToNS moves the adapter with interface name `ifStr` to the network namespace
2441// of `pid`.
2542func MoveInterfaceToNS (ifStr string , pid int ) error {
@@ -67,7 +84,7 @@ func DoInNetNS(ns netns.NsHandle, run func() error) error {
6784//
6885// This function MUST be used in tandem with `DoInNetNS` or some other means that ensures that the goroutine
6986// executing this code stays on the same thread.
70- func NetNSConfig (ctx context.Context , ifStr string , nsPid int , adapter * prot. NetworkAdapter ) error {
87+ func NetNSConfig (ctx context.Context , ifStr string , nsPid int , adapter * guestresource. LCOWNetworkAdapter ) error {
7188 ctx , entry := log .S (ctx , logrus.Fields {
7289 "ifname" : ifStr ,
7390 "pid" : nsPid ,
@@ -101,29 +118,14 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net
101118 }
102119
103120 // Configure the interface
104- if adapter .NatEnabled {
105- entry .Tracef ("Configuring interface with NAT: %s/%d gw=%s" ,
106- adapter .AllocatedIPAddress ,
107- adapter .HostIPPrefixLength , adapter .HostIPAddress )
108- metric := 1
109- if adapter .EnableLowMetric {
110- metric = 500
111- }
121+ if len (adapter .IPConfigs ) != 0 {
122+ entry .Debugf ("Configuring interface with NAT: %v" , adapter )
112123
113124 // Bring the interface up
114125 if err := netlink .LinkSetUp (link ); err != nil {
115- return errors .Wrapf (err , "netlink.LinkSetUp(%#v) failed" , link )
116- }
117- if err := assignIPToLink (ctx , ifStr , nsPid , link ,
118- adapter .AllocatedIPAddress , adapter .HostIPAddress , adapter .HostIPPrefixLength ,
119- adapter .EnableLowMetric , metric ,
120- ); err != nil {
121- return err
126+ return fmt .Errorf ("netlink.LinkSetUp(%#v) failed: %w" , link , err )
122127 }
123- if err := assignIPToLink (ctx , ifStr , nsPid , link ,
124- adapter .AllocatedIPv6Address , adapter .HostIPv6Address , adapter .HostIPv6PrefixLength ,
125- adapter .EnableLowMetric , metric ,
126- ); err != nil {
128+ if err := configureLink (ctx , link , adapter ); err != nil {
127129 return err
128130 }
129131 } else {
@@ -186,107 +188,104 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net
186188 return nil
187189}
188190
189- func assignIPToLink (ctx context.Context ,
190- ifStr string ,
191- nsPid int ,
191+ func configureLink (ctx context.Context ,
192192 link netlink.Link ,
193- allocatedIP string ,
194- gatewayIP string ,
195- prefixLen uint8 ,
196- enableLowMetric bool ,
197- metric int ,
193+ adapter * guestresource.LCOWNetworkAdapter ,
198194) error {
199- entry := log .G (ctx )
200- entry .WithFields (logrus.Fields {
201- "link" : link .Attrs ().Name ,
202- "IP" : allocatedIP ,
203- "prefixLen" : prefixLen ,
204- "gateway" : gatewayIP ,
205- "metric" : metric ,
206- }).Trace ("assigning IP address" )
207- if allocatedIP == "" {
208- return nil
209- }
210- // Set IP address
211- ip , addr , err := net .ParseCIDR (allocatedIP + "/" + strconv .FormatUint (uint64 (prefixLen ), 10 ))
212- if err != nil {
213- return errors .Wrapf (err , "parsing address %s/%d failed" , allocatedIP , prefixLen )
214- }
215- // the IP address field in addr is masked, so replace it with the original ip address
216- addr .IP = ip
217- entry .WithFields (logrus.Fields {
218- "allocatedIP" : ip ,
219- "IP" : addr ,
220- }).Debugf ("parsed ip address %s/%d" , allocatedIP , prefixLen )
221- ipAddr := & netlink.Addr {IPNet : addr , Label : "" }
222- if err := netlink .AddrAdd (link , ipAddr ); err != nil {
223- return errors .Wrapf (err , "netlink.AddrAdd(%#v, %#v) failed" , link , ipAddr )
224- }
225- if gatewayIP == "" {
226- return nil
227- }
228- // Set gateway
229- gw := net .ParseIP (gatewayIP )
230- if gw == nil {
231- return errors .Wrapf (err , "parsing gateway address %s failed" , gatewayIP )
232- }
195+ for _ , ipConfig := range adapter .IPConfigs {
196+ log .G (ctx ).WithFields (logrus.Fields {
197+ "link" : link .Attrs ().Name ,
198+ "IP" : ipConfig .IPAddress ,
199+ "prefixLen" : ipConfig .PrefixLength ,
200+ }).Debug ("assigning IP address" )
233201
234- if ! addr .Contains (gw ) {
235- // In the case that a gw is not part of the subnet we are setting gw for,
236- // a new addr containing this gw address need to be added into the link to avoid getting
237- // unreachable error when adding this out-of-subnet gw route
238- entry .Debugf ("gw is outside of the subnet: Configure %s in %d with: %s/%d gw=%s\n " ,
239- ifStr , nsPid , allocatedIP , prefixLen , gatewayIP )
240-
241- // net library's ParseIP call always returns an array of length 16, so we
242- // need to first check if the address is IPv4 or IPv6 before calculating
243- // the mask length. See https://pkg.go.dev/net#ParseIP.
244- ml := 8
245- if gw .To4 () != nil {
246- ml *= net .IPv4len
247- } else if gw .To16 () != nil {
248- ml *= net .IPv6len
249- } else {
250- return fmt .Errorf ("gw IP is neither IPv4 nor IPv6: %v" , gw )
202+ // Set IP address
203+ ip , addr , err := net .ParseCIDR (ipConfig .IPAddress + "/" + strconv .FormatUint (uint64 (ipConfig .PrefixLength ), 10 ))
204+ if err != nil {
205+ return fmt .Errorf ("parsing address %s/%d failed: %w" , ipConfig .IPAddress , ipConfig .PrefixLength , err )
251206 }
252- addr2 := & net.IPNet {
253- IP : gw ,
254- Mask : net .CIDRMask (ml , ml )}
255- ipAddr2 := & netlink.Addr {IPNet : addr2 , Label : "" }
256- if err := netlink .AddrAdd (link , ipAddr2 ); err != nil {
257- return errors .Wrapf (err , "netlink.AddrAdd(%#v, %#v) failed" , link , ipAddr2 )
207+ // the IP address field in addr is masked, so replace it with the original ip address
208+ addr .IP = ip
209+ log .G (ctx ).WithFields (logrus.Fields {
210+ "allocatedIP" : ip ,
211+ "IP" : addr ,
212+ }).Debugf ("parsed ip address %s/%d" , ipConfig .IPAddress , ipConfig .PrefixLength )
213+ ipAddr := & netlink.Addr {IPNet : addr , Label : "" }
214+ if err := netlinkAddrAdd (link , ipAddr ); err != nil {
215+ return fmt .Errorf ("netlink.AddrAdd(%#v, %#v) failed: %w" , link , ipAddr , err )
258216 }
259217 }
260218
261- var table int
262- if enableLowMetric {
263- // add a route rule for the new interface so packets coming on this interface
264- // always go out the same interface
265- _ , ml := addr .Mask .Size ()
266- srcNet := & net.IPNet {
267- IP : net .ParseIP (allocatedIP ),
268- Mask : net .CIDRMask (ml , ml ),
219+ for _ , r := range adapter .Routes {
220+ log .G (ctx ).WithField ("route" , r ).Debugf ("adding a route to interface %s" , link .Attrs ().Name )
221+
222+ if (r .DestinationPrefix == ipv4GwDestination || r .DestinationPrefix == ipv6GwDestination ) &&
223+ (r .NextHop == ipv4EmptyGw || r .NextHop == ipv6EmptyGw ) {
224+ // this indicates no default gateway was added for this interface
225+ continue
269226 }
270- rule := netlink .NewRule ()
271- rule .Table = 101
272- rule .Src = srcNet
273- rule .Priority = 5
274227
275- if err := netlink .RuleAdd (rule ); err != nil {
276- return errors .Wrapf (err , "netlink.RuleAdd(%#v) failed" , rule )
228+ // dst will be nil when setting default gateways
229+ var dst * net.IPNet
230+ if ! (r .DestinationPrefix == ipv4GwDestination || r .DestinationPrefix == ipv6GwDestination ) {
231+ dstIP , dstAddr , err := net .ParseCIDR (r .DestinationPrefix )
232+ if err != nil {
233+ return fmt .Errorf ("parsing route dst address %s failed: %w" , r .DestinationPrefix , err )
234+ }
235+ dstAddr .IP = dstIP
236+ dst = dstAddr
237+ }
238+
239+ // gw can be nil when setting something like
240+ // ip route add 10.0.0.0/16 dev eth0
241+ gw := net .ParseIP (r .NextHop )
242+ if gw == nil && dst == nil {
243+ return fmt .Errorf ("gw and destination cannot both be nil" )
244+ }
245+
246+ route := netlink.Route {
247+ Scope : netlink .SCOPE_UNIVERSE ,
248+ LinkIndex : link .Attrs ().Index ,
249+ Gw : gw ,
250+ Dst : dst ,
251+ Priority : int (r .Metric ),
252+ }
253+ if err := netlinkRouteAdd (& route ); err != nil {
254+ // unfortunately, netlink library doesn't have great error handling,
255+ // so we have to rely on the string error here
256+ if strings .Contains (err .Error (), unreachableErr ) && gw != nil {
257+ // In the case that a gw is not part of the subnet we are setting gw for,
258+ // a new addr containing this gw address needs to be added into the link to avoid getting
259+ // unreachable error when adding this out-of-subnet gw route
260+ log .G (ctx ).Infof ("gw is outside of the subnet: %v" , gw )
261+
262+ // net library's ParseIP call always returns an array of length 16, so we
263+ // need to first check if the address is IPv4 or IPv6 before calculating
264+ // the mask length. See https://pkg.go.dev/net#ParseIP.
265+ ml := 8
266+ if gw .To4 () != nil {
267+ ml *= net .IPv4len
268+ } else if gw .To16 () != nil {
269+ ml *= net .IPv6len
270+ } else {
271+ return fmt .Errorf ("gw IP is neither IPv4 nor IPv6: %v" , gw )
272+ }
273+ addr2 := & net.IPNet {
274+ IP : gw ,
275+ Mask : net .CIDRMask (ml , ml )}
276+ ipAddr2 := & netlink.Addr {IPNet : addr2 , Label : "" }
277+ if err := netlinkAddrAdd (link , ipAddr2 ); err != nil {
278+ return fmt .Errorf ("netlink.AddrAdd(%#v, %#v) failed: %w" , link , ipAddr2 , err )
279+ }
280+
281+ // try adding the route again
282+ if err := netlinkRouteAdd (& route ); err != nil {
283+ return fmt .Errorf ("netlink.RouteAdd(%#v) failed: %w" , route , err )
284+ }
285+ } else {
286+ return fmt .Errorf ("netlink.RouteAdd(%#v) failed: %w" , route , err )
287+ }
277288 }
278- table = rule .Table
279- }
280- // add the default route in that interface specific table
281- route := netlink.Route {
282- Scope : netlink .SCOPE_UNIVERSE ,
283- LinkIndex : link .Attrs ().Index ,
284- Gw : gw ,
285- Table : table ,
286- Priority : metric ,
287- }
288- if err := netlink .RouteAdd (& route ); err != nil {
289- return errors .Wrapf (err , "netlink.RouteAdd(%#v) failed" , route )
290289 }
291290 return nil
292291}
0 commit comments