diff --git a/pkg/tcpip/BUILD b/pkg/tcpip/BUILD index 0736a3f40..33baa5ff9 100644 --- a/pkg/tcpip/BUILD +++ b/pkg/tcpip/BUILD @@ -19,10 +19,23 @@ go_template_instance( }, ) +go_template_instance( + name = "route_list", + out = "route_list.go", + package = "tcpip", + prefix = "Route", + template = "//pkg/ilist:generic_list", + types = { + "Element": "*Route", + "Linker": "*Route", + }, +) + go_library( name = "tcpip", srcs = [ "errors.go", + "route_list.go", "sock_err_list.go", "socketops.go", "stdclock.go", diff --git a/pkg/tcpip/header/ipv4.go b/pkg/tcpip/header/ipv4.go index 84a5b1d77..d6801199a 100644 --- a/pkg/tcpip/header/ipv4.go +++ b/pkg/tcpip/header/ipv4.go @@ -178,6 +178,9 @@ var ( // IPv4AllRoutersGroup is a multicast address for all routers. IPv4AllRoutersGroup = tcpip.AddrFrom4([4]byte{0xe0, 0x00, 0x00, 0x02}) + + // IPv4Loopback is the loopback IPv4 address. + IPv4Loopback = tcpip.AddrFrom4([4]byte{0x7f, 0x00, 0x00, 0x01}) ) // Flags that may be set in an IPv4 packet. diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 61b91aa06..8b4711a81 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -88,8 +88,9 @@ type Stack struct { // routeMu protects annotated fields below. routeMu routeStackRWMutex `state:"nosave"` + // routeTable is a list of routes sorted by prefix length, longest (most specific) first. // +checklocks:routeMu - routeTable []tcpip.Route + routeTable tcpip.RouteList mu stackRWMutex `state:"nosave"` // +checklocks:mu @@ -280,7 +281,7 @@ type TransportEndpointInfo struct { // incompatible with the receiver. // // Preconditon: the parent endpoint mu must be held while calling this method. -func (t *TransportEndpointInfo) AddrNetProtoLocked(addr tcpip.FullAddress, v6only bool) (tcpip.FullAddress, tcpip.NetworkProtocolNumber, tcpip.Error) { +func (t *TransportEndpointInfo) AddrNetProtoLocked(addr tcpip.FullAddress, v6only bool, bind bool) (tcpip.FullAddress, tcpip.NetworkProtocolNumber, tcpip.Error) { netProto := t.NetProto switch addr.Addr.BitLen() { case header.IPv4AddressSizeBits: @@ -306,6 +307,22 @@ func (t *TransportEndpointInfo) AddrNetProtoLocked(addr tcpip.FullAddress, v6onl } } + if !bind && addr.Addr.Unspecified() { + // If the destination address isn't set, Linux sets it to the + // source address. If a source address isn't set either, it + // sets both to the loopback address. + if t.ID.LocalAddress.Unspecified() { + switch netProto { + case header.IPv4ProtocolNumber: + addr.Addr = header.IPv4Loopback + case header.IPv6ProtocolNumber: + addr.Addr = header.IPv6Loopback + } + } else { + addr.Addr = t.ID.LocalAddress + } + } + switch { case netProto == t.NetProto: case netProto == header.IPv4ProtocolNumber && t.NetProto == header.IPv6ProtocolNumber: @@ -729,21 +746,41 @@ func (s *Stack) SetPortRange(start uint16, end uint16) tcpip.Error { func (s *Stack) SetRouteTable(table []tcpip.Route) { s.routeMu.Lock() defer s.routeMu.Unlock() - s.routeTable = table + s.routeTable.Reset() + for _, r := range table { + s.addRouteLocked(&r) + } } // GetRouteTable returns the route table which is currently in use. func (s *Stack) GetRouteTable() []tcpip.Route { s.routeMu.RLock() defer s.routeMu.RUnlock() - return append([]tcpip.Route(nil), s.routeTable...) + table := make([]tcpip.Route, 0) + for r := s.routeTable.Front(); r != nil; r = r.Next() { + table = append(table, *r) + } + return table } // AddRoute appends a route to the route table. func (s *Stack) AddRoute(route tcpip.Route) { s.routeMu.Lock() defer s.routeMu.Unlock() - s.routeTable = append(s.routeTable, route) + s.addRouteLocked(&route) +} + +// +checklocks:s.routeMu +func (s *Stack) addRouteLocked(route *tcpip.Route) { + routePrefix := route.Destination.Prefix() + n := s.routeTable.Front() + for ; n != nil; n = n.Next() { + if n.Destination.Prefix() < routePrefix { + s.routeTable.InsertBefore(n, route) + return + } + } + s.routeTable.PushBack(route) } // RemoveRoutes removes matching routes from the route table. @@ -751,13 +788,13 @@ func (s *Stack) RemoveRoutes(match func(tcpip.Route) bool) { s.routeMu.Lock() defer s.routeMu.Unlock() - var filteredRoutes []tcpip.Route - for _, route := range s.routeTable { - if !match(route) { - filteredRoutes = append(filteredRoutes, route) + for route := s.routeTable.Front(); route != nil; { + next := route.Next() + if match(*route) { + s.routeTable.Remove(route) } + route = next } - s.routeTable = filteredRoutes } // NewEndpoint creates a new transport layer endpoint of the given protocol. @@ -970,16 +1007,13 @@ func (s *Stack) removeNICLocked(id tcpip.NICID) tcpip.Error { // Remove routes in-place. n tracks the number of routes written. s.routeMu.Lock() - n := 0 - for _, r := range s.routeTable { - if r.NIC != id { - // Keep this route. - s.routeTable[n] = r - n++ + for r := s.routeTable.Front(); r != nil; { + next := r.Next() + if r.NIC == id { + s.routeTable.Remove(r) } + r = next } - clear(s.routeTable[n:]) - s.routeTable = s.routeTable[:n] s.routeMu.Unlock() return nic.remove() @@ -1425,7 +1459,7 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n s.routeMu.RLock() defer s.routeMu.RUnlock() - for _, route := range s.routeTable { + for route := s.routeTable.Front(); route != nil; route = route.Next() { if remoteAddr.BitLen() != 0 && !route.Destination.Contains(remoteAddr) { continue } @@ -1464,7 +1498,7 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n locallyGenerated := (id != 0 || localAddr != tcpip.Address{}) if onlyGlobalAddresses && chosenRoute.Equal(tcpip.Route{}) && isNICForwarding(nic, netProto) { if locallyGenerated { - chosenRoute = route + chosenRoute = *route continue } diff --git a/pkg/tcpip/stack/stack_test.go b/pkg/tcpip/stack/stack_test.go index cf0605d80..a85d1d282 100644 --- a/pkg/tcpip/stack/stack_test.go +++ b/pkg/tcpip/stack/stack_test.go @@ -4402,14 +4402,14 @@ func TestAddRoute(t *testing.T) { t.Fatal(err) } - subnet2, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), tcpip.MaskFrom("\x01\x00\x00\x00")) + subnet2, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), tcpip.MaskFrom("\xff\x00\x00\x00")) if err != nil { t.Fatal(err) } expected := []tcpip.Route{ - {Destination: subnet1, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}, {Destination: subnet2, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}, + {Destination: subnet1, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}, } // Initialize the route table with one route. @@ -4423,7 +4423,7 @@ func TestAddRoute(t *testing.T) { t.Fatalf("Unexpected route table length got = %d, want = %d", got, want) } for i, route := range rt { - if got, want := route, expected[i]; got != want { + if got, want := route, expected[i]; !got.Equal(want) { t.Fatalf("Unexpected route got = %#v, want = %#v", got, want) } } diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index de6e03d07..33c7ddab4 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -1513,6 +1513,8 @@ func GetStackReceiveBufferLimits(so StackHandler) ReceiveBufferSizeOption { // // +stateify savable type Route struct { + RouteEntry + // Destination must contain the target address for this row to be viable. Destination Subnet diff --git a/pkg/tcpip/tests/integration/route_test.go b/pkg/tcpip/tests/integration/route_test.go index e7ce41640..dd73aeccc 100644 --- a/pkg/tcpip/tests/integration/route_test.go +++ b/pkg/tcpip/tests/integration/route_test.go @@ -131,7 +131,7 @@ func TestLocalPing(t *testing.T) { netProto: ipv4.ProtocolNumber, linkEndpoint: loopback.New, icmpBuf: ipv4ICMPBuf, - expectedConnectErr: &tcpip.ErrHostUnreachable{}, + expectedConnectErr: &tcpip.ErrNetworkUnreachable{}, checkLinkEndpoint: func(*testing.T, stack.LinkEndpoint) {}, }, { @@ -140,7 +140,7 @@ func TestLocalPing(t *testing.T) { netProto: ipv6.ProtocolNumber, linkEndpoint: loopback.New, icmpBuf: ipv6ICMPBuf, - expectedConnectErr: &tcpip.ErrHostUnreachable{}, + expectedConnectErr: &tcpip.ErrBadLocalAddress{}, checkLinkEndpoint: func(*testing.T, stack.LinkEndpoint) {}, }, { @@ -149,7 +149,7 @@ func TestLocalPing(t *testing.T) { netProto: ipv4.ProtocolNumber, linkEndpoint: channelEP, icmpBuf: ipv4ICMPBuf, - expectedConnectErr: &tcpip.ErrHostUnreachable{}, + expectedConnectErr: &tcpip.ErrNetworkUnreachable{}, checkLinkEndpoint: channelEPCheck, }, { @@ -158,7 +158,7 @@ func TestLocalPing(t *testing.T) { netProto: ipv6.ProtocolNumber, linkEndpoint: channelEP, icmpBuf: ipv6ICMPBuf, - expectedConnectErr: &tcpip.ErrHostUnreachable{}, + expectedConnectErr: &tcpip.ErrBadLocalAddress{}, checkLinkEndpoint: channelEPCheck, }, } diff --git a/pkg/tcpip/transport/internal/network/endpoint.go b/pkg/tcpip/transport/internal/network/endpoint.go index 745387af1..9b77ae36d 100644 --- a/pkg/tcpip/transport/internal/network/endpoint.go +++ b/pkg/tcpip/transport/internal/network/endpoint.go @@ -516,7 +516,7 @@ func (e *Endpoint) AcquireContextForWrite(opts tcpip.WriteOptions) (WriteContext } } - dst, netProto, err := e.checkV4Mapped(*to) + dst, netProto, err := e.checkV4Mapped(*to, false /* bind */) if err != nil { return WriteContext{}, err } @@ -656,7 +656,7 @@ func (e *Endpoint) ConnectAndThen(addr tcpip.FullAddress, f func(netProto tcpip. return &tcpip.ErrInvalidEndpointState{} } - addr, netProto, err := e.checkV4Mapped(addr) + addr, netProto, err := e.checkV4Mapped(addr, false /* bind */) if err != nil { return err } @@ -710,9 +710,9 @@ func (e *Endpoint) Shutdown() tcpip.Error { // checkV4MappedRLocked determines the effective network protocol and converts // addr to its canonical form. -func (e *Endpoint) checkV4Mapped(addr tcpip.FullAddress) (tcpip.FullAddress, tcpip.NetworkProtocolNumber, tcpip.Error) { +func (e *Endpoint) checkV4Mapped(addr tcpip.FullAddress, bind bool) (tcpip.FullAddress, tcpip.NetworkProtocolNumber, tcpip.Error) { info := e.Info() - unwrapped, netProto, err := info.AddrNetProtoLocked(addr, e.ops.GetV6Only()) + unwrapped, netProto, err := info.AddrNetProtoLocked(addr, e.ops.GetV6Only(), bind) if err != nil { return tcpip.FullAddress{}, 0, err } @@ -747,7 +747,7 @@ func (e *Endpoint) BindAndThen(addr tcpip.FullAddress, f func(tcpip.NetworkProto return &tcpip.ErrInvalidEndpointState{} } - addr, netProto, err := e.checkV4Mapped(addr) + addr, netProto, err := e.checkV4Mapped(addr, true /* bind */) if err != nil { return err } @@ -907,7 +907,7 @@ func (e *Endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error { defer e.mu.Unlock() fa := tcpip.FullAddress{Addr: v.InterfaceAddr} - fa, netProto, err := e.checkV4Mapped(fa) + fa, netProto, err := e.checkV4Mapped(fa, true /* bind */) if err != nil { return err } diff --git a/pkg/tcpip/transport/tcp/endpoint.go b/pkg/tcpip/transport/tcp/endpoint.go index 03b6f1d9f..5cd028b48 100644 --- a/pkg/tcpip/transport/tcp/endpoint.go +++ b/pkg/tcpip/transport/tcp/endpoint.go @@ -2220,8 +2220,8 @@ func (e *Endpoint) GetSockOpt(opt tcpip.GettableSocketOption) tcpip.Error { // checkV4MappedLocked determines the effective network protocol and converts // addr to its canonical form. // +checklocks:e.mu -func (e *Endpoint) checkV4MappedLocked(addr tcpip.FullAddress) (tcpip.FullAddress, tcpip.NetworkProtocolNumber, tcpip.Error) { - unwrapped, netProto, err := e.TransportEndpointInfo.AddrNetProtoLocked(addr, e.ops.GetV6Only()) +func (e *Endpoint) checkV4MappedLocked(addr tcpip.FullAddress, bind bool) (tcpip.FullAddress, tcpip.NetworkProtocolNumber, tcpip.Error) { + unwrapped, netProto, err := e.TransportEndpointInfo.AddrNetProtoLocked(addr, e.ops.GetV6Only(), bind) if err != nil { return tcpip.FullAddress{}, 0, err } @@ -2388,7 +2388,7 @@ func (e *Endpoint) registerEndpoint(addr tcpip.FullAddress, netProto tcpip.Netwo func (e *Endpoint) connect(addr tcpip.FullAddress, handshake bool) tcpip.Error { connectingAddr := addr.Addr - addr, netProto, err := e.checkV4MappedLocked(addr) + addr, netProto, err := e.checkV4MappedLocked(addr, false /* bind */) if err != nil { return err } @@ -2740,7 +2740,7 @@ func (e *Endpoint) bindLocked(addr tcpip.FullAddress) (err tcpip.Error) { } e.BindAddr = addr.Addr - addr, netProto, err := e.checkV4MappedLocked(addr) + addr, netProto, err := e.checkV4MappedLocked(addr, true /* bind */) if err != nil { return err } diff --git a/pkg/tcpip/transport/tcp/endpoint_state.go b/pkg/tcpip/transport/tcp/endpoint_state.go index 5a6d2f6a4..63457f7f8 100644 --- a/pkg/tcpip/transport/tcp/endpoint_state.go +++ b/pkg/tcpip/transport/tcp/endpoint_state.go @@ -140,7 +140,7 @@ func (e *Endpoint) Restore(s *stack.Stack) { bind := func() { e.mu.Lock() defer e.mu.Unlock() - addr, _, err := e.checkV4MappedLocked(tcpip.FullAddress{Addr: e.BindAddr, Port: e.TransportEndpointInfo.ID.LocalPort}) + addr, _, err := e.checkV4MappedLocked(tcpip.FullAddress{Addr: e.BindAddr, Port: e.TransportEndpointInfo.ID.LocalPort}, true /* bind */) if err != nil { panic("unable to parse BindAddr: " + err.String()) }