@@ -10,16 +10,32 @@ 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+ netlinkRouteAdd = netlink .RouteAdd
28+ )
29+
30+ const (
31+ ipv4GwDestination = "0.0.0.0/0"
32+ ipv4EmptyGw = "0.0.0.0"
33+ ipv6GwDestination = "::/0"
34+ ipv6EmptyGw = "::"
35+
36+ unreachableErr = "network is unreachable"
37+ )
38+
2339// MoveInterfaceToNS moves the adapter with interface name `ifStr` to the network namespace
2440// of `pid`.
2541func MoveInterfaceToNS (ifStr string , pid int ) error {
@@ -67,7 +83,7 @@ func DoInNetNS(ns netns.NsHandle, run func() error) error {
6783//
6884// This function MUST be used in tandem with `DoInNetNS` or some other means that ensures that the goroutine
6985// executing this code stays on the same thread.
70- func NetNSConfig (ctx context.Context , ifStr string , nsPid int , adapter * prot. NetworkAdapter ) error {
86+ func NetNSConfig (ctx context.Context , ifStr string , nsPid int , adapter * guestresource. LCOWNetworkAdapter ) error {
7187 ctx , entry := log .S (ctx , logrus.Fields {
7288 "ifname" : ifStr ,
7389 "pid" : nsPid ,
@@ -101,29 +117,14 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net
101117 }
102118
103119 // 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- }
120+ if len (adapter .IPConfigs ) != 0 {
121+ entry .Debugf ("Configuring interface with NAT: %v" , adapter )
112122
113123 // Bring the interface up
114124 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
125+ return fmt .Errorf ("netlink.LinkSetUp(%#v) failed: %w" , link , err )
122126 }
123- if err := assignIPToLink (ctx , ifStr , nsPid , link ,
124- adapter .AllocatedIPv6Address , adapter .HostIPv6Address , adapter .HostIPv6PrefixLength ,
125- adapter .EnableLowMetric , metric ,
126- ); err != nil {
127+ if err := configureLink (ctx , link , adapter ); err != nil {
127128 return err
128129 }
129130 } else {
@@ -186,107 +187,104 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net
186187 return nil
187188}
188189
189- func assignIPToLink (ctx context.Context ,
190- ifStr string ,
191- nsPid int ,
190+ func configureLink (ctx context.Context ,
192191 link netlink.Link ,
193- allocatedIP string ,
194- gatewayIP string ,
195- prefixLen uint8 ,
196- enableLowMetric bool ,
197- metric int ,
192+ adapter * guestresource.LCOWNetworkAdapter ,
198193) 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- }
194+ for _ , ipConfig := range adapter .IPConfigs {
195+ log .G (ctx ).WithFields (logrus.Fields {
196+ "link" : link .Attrs ().Name ,
197+ "IP" : ipConfig .IPAddress ,
198+ "prefixLen" : ipConfig .PrefixLength ,
199+ }).Debug ("assigning IP address" )
233200
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 )
201+ // Set IP address
202+ ip , addr , err := net .ParseCIDR (ipConfig .IPAddress + "/" + strconv .FormatUint (uint64 (ipConfig .PrefixLength ), 10 ))
203+ if err != nil {
204+ return fmt .Errorf ("parsing address %s/%d failed: %w" , ipConfig .IPAddress , ipConfig .PrefixLength , err )
251205 }
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 )
206+ // the IP address field in addr is masked, so replace it with the original ip address
207+ addr .IP = ip
208+ log .G (ctx ).WithFields (logrus.Fields {
209+ "allocatedIP" : ip ,
210+ "IP" : addr ,
211+ }).Debugf ("parsed ip address %s/%d" , ipConfig .IPAddress , ipConfig .PrefixLength )
212+ ipAddr := & netlink.Addr {IPNet : addr , Label : "" }
213+ if err := netlinkAddrAdd (link , ipAddr ); err != nil {
214+ return fmt .Errorf ("netlink.AddrAdd(%#v, %#v) failed: %w" , link , ipAddr , err )
258215 }
259216 }
260217
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 ),
218+ for _ , r := range adapter .Routes {
219+ log .G (ctx ).WithField ("route" , r ).Debugf ("adding a route to interface %s" , link .Attrs ().Name )
220+
221+ if (r .DestinationPrefix == ipv4GwDestination || r .DestinationPrefix == ipv6GwDestination ) &&
222+ (r .NextHop == ipv4EmptyGw || r .NextHop == ipv6EmptyGw ) {
223+ // this indicates no default gateway was added for this interface
224+ continue
269225 }
270- rule := netlink .NewRule ()
271- rule .Table = 101
272- rule .Src = srcNet
273- rule .Priority = 5
274226
275- if err := netlink .RuleAdd (rule ); err != nil {
276- return errors .Wrapf (err , "netlink.RuleAdd(%#v) failed" , rule )
227+ // dst will be nil when setting default gateways
228+ var dst * net.IPNet
229+ if ! (r .DestinationPrefix == ipv4GwDestination || r .DestinationPrefix == ipv6GwDestination ) {
230+ dstIP , dstAddr , err := net .ParseCIDR (r .DestinationPrefix )
231+ if err != nil {
232+ return fmt .Errorf ("parsing route dst address %s failed: %w" , r .DestinationPrefix , err )
233+ }
234+ dstAddr .IP = dstIP
235+ dst = dstAddr
236+ }
237+
238+ // gw can be nil when setting something like
239+ // ip route add 10.0.0.0/16 dev eth0
240+ gw := net .ParseIP (r .NextHop )
241+ if gw == nil && dst == nil {
242+ return fmt .Errorf ("gw and destination cannot both be nil" )
243+ }
244+
245+ route := netlink.Route {
246+ Scope : netlink .SCOPE_UNIVERSE ,
247+ LinkIndex : link .Attrs ().Index ,
248+ Gw : gw ,
249+ Dst : dst ,
250+ Priority : int (r .Metric ),
251+ }
252+ if err := netlinkRouteAdd (& route ); err != nil {
253+ // unfortunately, netlink library doesn't have great error handling,
254+ // so we have to rely on the string error here
255+ if strings .Contains (err .Error (), unreachableErr ) && gw != nil {
256+ // In the case that a gw is not part of the subnet we are setting gw for,
257+ // a new addr containing this gw address needs to be added into the link to avoid getting
258+ // unreachable error when adding this out-of-subnet gw route
259+ log .G (ctx ).Infof ("gw is outside of the subnet: %v" , gw )
260+
261+ // net library's ParseIP call always returns an array of length 16, so we
262+ // need to first check if the address is IPv4 or IPv6 before calculating
263+ // the mask length. See https://pkg.go.dev/net#ParseIP.
264+ ml := 8
265+ if gw .To4 () != nil {
266+ ml *= net .IPv4len
267+ } else if gw .To16 () != nil {
268+ ml *= net .IPv6len
269+ } else {
270+ return fmt .Errorf ("gw IP is neither IPv4 nor IPv6: %v" , gw )
271+ }
272+ addr2 := & net.IPNet {
273+ IP : gw ,
274+ Mask : net .CIDRMask (ml , ml )}
275+ ipAddr2 := & netlink.Addr {IPNet : addr2 , Label : "" }
276+ if err := netlinkAddrAdd (link , ipAddr2 ); err != nil {
277+ return fmt .Errorf ("netlink.AddrAdd(%#v, %#v) failed: %w" , link , ipAddr2 , err )
278+ }
279+
280+ // try adding the route again
281+ if err := netlinkRouteAdd (& route ); err != nil {
282+ return fmt .Errorf ("netlink.RouteAdd(%#v) failed: %w" , route , err )
283+ }
284+ } else {
285+ return fmt .Errorf ("netlink.RouteAdd(%#v) failed: %w" , route , err )
286+ }
277287 }
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 )
290288 }
291289 return nil
292290}
0 commit comments