Use brodcast MAC for broadcast IPv4 packets

When sending packets to a known network's broadcast address, use the
broadcast MAC address.

Test:
- stack_test.TestOutgoingSubnetBroadcast
- udp_test.TestOutgoingSubnetBroadcast
PiperOrigin-RevId: 324062407
This commit is contained in:
Ghanan Gowripalan
2020-07-30 12:50:02 -07:00
committed by gVisor bot
parent bc8201d01b
commit b00858d075
7 changed files with 464 additions and 8 deletions
+1
View File
@@ -110,6 +110,7 @@ go_test(
"//pkg/tcpip/transport/udp",
"//pkg/waiter",
"@com_github_google_go_cmp//cmp:go_default_library",
"@com_github_google_go_cmp//cmp/cmpopts:go_default_library",
],
)
+10
View File
@@ -48,6 +48,10 @@ type Route struct {
// Loop controls where WritePacket should send packets.
Loop PacketLooping
// directedBroadcast indicates whether this route is sending a directed
// broadcast packet.
directedBroadcast bool
}
// makeRoute initializes a new route. It takes ownership of the provided
@@ -275,6 +279,12 @@ func (r *Route) Stack() *Stack {
return r.ref.stack()
}
// IsBroadcast returns true if the route is to send a broadcast packet.
func (r *Route) IsBroadcast() bool {
// Only IPv4 has a notion of broadcast.
return r.directedBroadcast || r.RemoteAddress == header.IPv4Broadcast
}
// ReverseRoute returns new route with given source and destination address.
func (r *Route) ReverseRoute(src tcpip.Address, dst tcpip.Address) Route {
return Route{
+11 -4
View File
@@ -1284,9 +1284,9 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n
s.mu.RLock()
defer s.mu.RUnlock()
isBroadcast := remoteAddr == header.IPv4Broadcast
isLocalBroadcast := remoteAddr == header.IPv4Broadcast
isMulticast := header.IsV4MulticastAddress(remoteAddr) || header.IsV6MulticastAddress(remoteAddr)
needRoute := !(isBroadcast || isMulticast || header.IsV6LinkLocalAddress(remoteAddr))
needRoute := !(isLocalBroadcast || isMulticast || header.IsV6LinkLocalAddress(remoteAddr))
if id != 0 && !needRoute {
if nic, ok := s.nics[id]; ok && nic.enabled() {
if ref := s.getRefEP(nic, localAddr, remoteAddr, netProto); ref != nil {
@@ -1307,9 +1307,16 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n
}
r := makeRoute(netProto, ref.ep.ID().LocalAddress, remoteAddr, nic.linkEP.LinkAddress(), ref, s.handleLocal && !nic.isLoopback(), multicastLoop && !nic.isLoopback())
if needRoute {
r.NextHop = route.Gateway
r.directedBroadcast = route.Destination.IsBroadcast(remoteAddr)
if len(route.Gateway) > 0 {
if needRoute {
r.NextHop = route.Gateway
}
} else if r.directedBroadcast {
r.RemoteLinkAddress = header.EthernetBroadcastAddress
}
return r, nil
}
}
+223
View File
@@ -27,6 +27,7 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
@@ -3418,3 +3419,225 @@ func TestStackSendBufferSizeOption(t *testing.T) {
})
}
}
func TestOutgoingSubnetBroadcast(t *testing.T) {
const (
unspecifiedNICID = 0
nicID1 = 1
)
defaultAddr := tcpip.AddressWithPrefix{
Address: header.IPv4Any,
PrefixLen: 0,
}
defaultSubnet := defaultAddr.Subnet()
ipv4Addr := tcpip.AddressWithPrefix{
Address: "\xc0\xa8\x01\x3a",
PrefixLen: 24,
}
ipv4Subnet := ipv4Addr.Subnet()
ipv4SubnetBcast := ipv4Subnet.Broadcast()
ipv4Gateway := tcpip.Address("\xc0\xa8\x01\x01")
ipv4AddrPrefix31 := tcpip.AddressWithPrefix{
Address: "\xc0\xa8\x01\x3a",
PrefixLen: 31,
}
ipv4Subnet31 := ipv4AddrPrefix31.Subnet()
ipv4Subnet31Bcast := ipv4Subnet31.Broadcast()
ipv4AddrPrefix32 := tcpip.AddressWithPrefix{
Address: "\xc0\xa8\x01\x3a",
PrefixLen: 32,
}
ipv4Subnet32 := ipv4AddrPrefix32.Subnet()
ipv4Subnet32Bcast := ipv4Subnet32.Broadcast()
ipv6Addr := tcpip.AddressWithPrefix{
Address: "\x20\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
PrefixLen: 64,
}
ipv6Subnet := ipv6Addr.Subnet()
ipv6SubnetBcast := ipv6Subnet.Broadcast()
remNetAddr := tcpip.AddressWithPrefix{
Address: "\x64\x0a\x7b\x18",
PrefixLen: 24,
}
remNetSubnet := remNetAddr.Subnet()
remNetSubnetBcast := remNetSubnet.Broadcast()
tests := []struct {
name string
nicAddr tcpip.ProtocolAddress
routes []tcpip.Route
remoteAddr tcpip.Address
expectedRoute stack.Route
}{
// Broadcast to a locally attached subnet populates the broadcast MAC.
{
name: "IPv4 Broadcast to local subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4Addr,
},
routes: []tcpip.Route{
{
Destination: ipv4Subnet,
NIC: nicID1,
},
},
remoteAddr: ipv4SubnetBcast,
expectedRoute: stack.Route{
LocalAddress: ipv4Addr.Address,
RemoteAddress: ipv4SubnetBcast,
RemoteLinkAddress: header.EthernetBroadcastAddress,
NetProto: header.IPv4ProtocolNumber,
Loop: stack.PacketOut,
},
},
// Broadcast to a locally attached /31 subnet does not populate the
// broadcast MAC.
{
name: "IPv4 Broadcast to local /31 subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4AddrPrefix31,
},
routes: []tcpip.Route{
{
Destination: ipv4Subnet31,
NIC: nicID1,
},
},
remoteAddr: ipv4Subnet31Bcast,
expectedRoute: stack.Route{
LocalAddress: ipv4AddrPrefix31.Address,
RemoteAddress: ipv4Subnet31Bcast,
NetProto: header.IPv4ProtocolNumber,
Loop: stack.PacketOut,
},
},
// Broadcast to a locally attached /32 subnet does not populate the
// broadcast MAC.
{
name: "IPv4 Broadcast to local /32 subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4AddrPrefix32,
},
routes: []tcpip.Route{
{
Destination: ipv4Subnet32,
NIC: nicID1,
},
},
remoteAddr: ipv4Subnet32Bcast,
expectedRoute: stack.Route{
LocalAddress: ipv4AddrPrefix32.Address,
RemoteAddress: ipv4Subnet32Bcast,
NetProto: header.IPv4ProtocolNumber,
Loop: stack.PacketOut,
},
},
// IPv6 has no notion of a broadcast.
{
name: "IPv6 'Broadcast' to local subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: ipv6Addr,
},
routes: []tcpip.Route{
{
Destination: ipv6Subnet,
NIC: nicID1,
},
},
remoteAddr: ipv6SubnetBcast,
expectedRoute: stack.Route{
LocalAddress: ipv6Addr.Address,
RemoteAddress: ipv6SubnetBcast,
NetProto: header.IPv6ProtocolNumber,
Loop: stack.PacketOut,
},
},
// Broadcast to a remote subnet in the route table is send to the next-hop
// gateway.
{
name: "IPv4 Broadcast to remote subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4Addr,
},
routes: []tcpip.Route{
{
Destination: remNetSubnet,
Gateway: ipv4Gateway,
NIC: nicID1,
},
},
remoteAddr: remNetSubnetBcast,
expectedRoute: stack.Route{
LocalAddress: ipv4Addr.Address,
RemoteAddress: remNetSubnetBcast,
NextHop: ipv4Gateway,
NetProto: header.IPv4ProtocolNumber,
Loop: stack.PacketOut,
},
},
// Broadcast to an unknown subnet follows the default route. Note that this
// is essentially just routing an unknown destination IP, because w/o any
// subnet prefix information a subnet broadcast address is just a normal IP.
{
name: "IPv4 Broadcast to unknown subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4Addr,
},
routes: []tcpip.Route{
{
Destination: defaultSubnet,
Gateway: ipv4Gateway,
NIC: nicID1,
},
},
remoteAddr: remNetSubnetBcast,
expectedRoute: stack.Route{
LocalAddress: ipv4Addr.Address,
RemoteAddress: remNetSubnetBcast,
NextHop: ipv4Gateway,
NetProto: header.IPv4ProtocolNumber,
Loop: stack.PacketOut,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol(), ipv6.NewProtocol()},
})
ep := channel.New(0, defaultMTU, "")
if err := s.CreateNIC(nicID1, ep); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
}
if err := s.AddProtocolAddress(nicID1, test.nicAddr); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v): %s", nicID1, test.nicAddr, err)
}
s.SetRouteTable(test.routes)
var netProto tcpip.NetworkProtocolNumber
switch l := len(test.remoteAddr); l {
case header.IPv4AddressSize:
netProto = header.IPv4ProtocolNumber
case header.IPv6AddressSize:
netProto = header.IPv6ProtocolNumber
default:
t.Fatalf("got unexpected address length = %d bytes", l)
}
if r, err := s.FindRoute(unspecifiedNICID, "" /* localAddr */, test.remoteAddr, netProto, false /* multicastLoop */); err != nil {
t.Fatalf("FindRoute(%d, '', %s, %d): %s", unspecifiedNICID, test.remoteAddr, netProto, err)
} else if diff := cmp.Diff(r, test.expectedRoute, cmpopts.IgnoreUnexported(r)); diff != "" {
t.Errorf("route mismatch (-want +got):\n%s", diff)
}
})
}
}
+26
View File
@@ -43,6 +43,9 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)
// Using header.IPv4AddressSize would cause an import cycle.
const ipv4AddressSize = 4
// Error represents an error in the netstack error space. Using a special type
// ensures that errors outside of this space are not accidentally introduced.
//
@@ -320,6 +323,29 @@ func (s *Subnet) Broadcast() Address {
return Address(addr)
}
// IsBroadcast returns true if the address is considered a broadcast address.
func (s *Subnet) IsBroadcast(address Address) bool {
// Only IPv4 supports the notion of a broadcast address.
if len(address) != ipv4AddressSize {
return false
}
// Normally, we would just compare address with the subnet's broadcast
// address but there is an exception where a simple comparison is not
// correct. This exception is for /31 and /32 IPv4 subnets where all
// addresses are considered valid host addresses.
//
// For /31 subnets, the case is easy. RFC 3021 Section 2.1 states that
// both addresses in a /31 subnet "MUST be interpreted as host addresses."
//
// For /32, the case is a bit more vague. RFC 3021 makes no mention of /32
// subnets. However, the same reasoning applies - if an exception is not
// made, then there do not exist any host addresses in a /32 subnet. RFC
// 4632 Section 3.1 also vaguely implies this interpretation by referring
// to addresses in /32 subnets as "host routes."
return s.Prefix() <= 30 && s.Broadcast() == address
}
// Equal returns true if s equals o.
//
// Needed to use cmp.Equal on Subnet as its fields are unexported.
+4 -4
View File
@@ -483,10 +483,6 @@ func (e *endpoint) write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, <-c
nicID = e.BindNICID
}
if to.Addr == header.IPv4Broadcast && !e.broadcast {
return 0, nil, tcpip.ErrBroadcastDisabled
}
dst, netProto, err := e.checkV4MappedLocked(*to)
if err != nil {
return 0, nil, err
@@ -503,6 +499,10 @@ func (e *endpoint) write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, <-c
resolve = route.Resolve
}
if !e.broadcast && route.IsBroadcast() {
return 0, nil, tcpip.ErrBroadcastDisabled
}
if route.IsResolutionRequired() {
if ch, err := resolve(nil); err != nil {
if err == tcpip.ErrWouldBlock {
+189
View File
@@ -2142,3 +2142,192 @@ func (c *testContext) checkEndpointReadStats(incr uint64, want tcpip.TransportEn
c.t.Errorf("Endpoint stats not matching for error %s got %+v want %+v", err, got, want)
}
}
func TestOutgoingSubnetBroadcast(t *testing.T) {
const nicID1 = 1
ipv4Addr := tcpip.AddressWithPrefix{
Address: "\xc0\xa8\x01\x3a",
PrefixLen: 24,
}
ipv4Subnet := ipv4Addr.Subnet()
ipv4SubnetBcast := ipv4Subnet.Broadcast()
ipv4Gateway := tcpip.Address("\xc0\xa8\x01\x01")
ipv4AddrPrefix31 := tcpip.AddressWithPrefix{
Address: "\xc0\xa8\x01\x3a",
PrefixLen: 31,
}
ipv4Subnet31 := ipv4AddrPrefix31.Subnet()
ipv4Subnet31Bcast := ipv4Subnet31.Broadcast()
ipv4AddrPrefix32 := tcpip.AddressWithPrefix{
Address: "\xc0\xa8\x01\x3a",
PrefixLen: 32,
}
ipv4Subnet32 := ipv4AddrPrefix32.Subnet()
ipv4Subnet32Bcast := ipv4Subnet32.Broadcast()
ipv6Addr := tcpip.AddressWithPrefix{
Address: "\x20\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
PrefixLen: 64,
}
ipv6Subnet := ipv6Addr.Subnet()
ipv6SubnetBcast := ipv6Subnet.Broadcast()
remNetAddr := tcpip.AddressWithPrefix{
Address: "\x64\x0a\x7b\x18",
PrefixLen: 24,
}
remNetSubnet := remNetAddr.Subnet()
remNetSubnetBcast := remNetSubnet.Broadcast()
tests := []struct {
name string
nicAddr tcpip.ProtocolAddress
routes []tcpip.Route
remoteAddr tcpip.Address
requiresBroadcastOpt bool
}{
{
name: "IPv4 Broadcast to local subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4Addr,
},
routes: []tcpip.Route{
{
Destination: ipv4Subnet,
NIC: nicID1,
},
},
remoteAddr: ipv4SubnetBcast,
requiresBroadcastOpt: true,
},
{
name: "IPv4 Broadcast to local /31 subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4AddrPrefix31,
},
routes: []tcpip.Route{
{
Destination: ipv4Subnet31,
NIC: nicID1,
},
},
remoteAddr: ipv4Subnet31Bcast,
requiresBroadcastOpt: false,
},
{
name: "IPv4 Broadcast to local /32 subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4AddrPrefix32,
},
routes: []tcpip.Route{
{
Destination: ipv4Subnet32,
NIC: nicID1,
},
},
remoteAddr: ipv4Subnet32Bcast,
requiresBroadcastOpt: false,
},
// IPv6 has no notion of a broadcast.
{
name: "IPv6 'Broadcast' to local subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: ipv6Addr,
},
routes: []tcpip.Route{
{
Destination: ipv6Subnet,
NIC: nicID1,
},
},
remoteAddr: ipv6SubnetBcast,
requiresBroadcastOpt: false,
},
{
name: "IPv4 Broadcast to remote subnet",
nicAddr: tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4Addr,
},
routes: []tcpip.Route{
{
Destination: remNetSubnet,
Gateway: ipv4Gateway,
NIC: nicID1,
},
},
remoteAddr: remNetSubnetBcast,
requiresBroadcastOpt: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol(), ipv6.NewProtocol()},
TransportProtocols: []stack.TransportProtocol{udp.NewProtocol()},
})
e := channel.New(0, defaultMTU, "")
if err := s.CreateNIC(nicID1, e); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
}
if err := s.AddProtocolAddress(nicID1, test.nicAddr); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v): %s", nicID1, test.nicAddr, err)
}
s.SetRouteTable(test.routes)
var netProto tcpip.NetworkProtocolNumber
switch l := len(test.remoteAddr); l {
case header.IPv4AddressSize:
netProto = header.IPv4ProtocolNumber
case header.IPv6AddressSize:
netProto = header.IPv6ProtocolNumber
default:
t.Fatalf("got unexpected address length = %d bytes", l)
}
wq := waiter.Queue{}
ep, err := s.NewEndpoint(udp.ProtocolNumber, netProto, &wq)
if err != nil {
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, netProto, err)
}
defer ep.Close()
data := tcpip.SlicePayload([]byte{1, 2, 3, 4})
to := tcpip.FullAddress{
Addr: test.remoteAddr,
Port: 80,
}
opts := tcpip.WriteOptions{To: &to}
expectedErrWithoutBcastOpt := tcpip.ErrBroadcastDisabled
if !test.requiresBroadcastOpt {
expectedErrWithoutBcastOpt = nil
}
if n, _, err := ep.Write(data, opts); err != expectedErrWithoutBcastOpt {
t.Fatalf("got ep.Write(_, _) = (%d, _, %v), want = (_, _, %v)", n, err, expectedErrWithoutBcastOpt)
}
if err := ep.SetSockOptBool(tcpip.BroadcastOption, true); err != nil {
t.Fatalf("got SetSockOptBool(BroadcastOption, true): %s", err)
}
if n, _, err := ep.Write(data, opts); err != nil {
t.Fatalf("got ep.Write(_, _) = (%d, _, %s), want = (_, _, nil)", n, err)
}
if err := ep.SetSockOptBool(tcpip.BroadcastOption, false); err != nil {
t.Fatalf("got SetSockOptBool(BroadcastOption, false): %s", err)
}
if n, _, err := ep.Write(data, opts); err != expectedErrWithoutBcastOpt {
t.Fatalf("got ep.Write(_, _) = (%d, _, %v), want = (_, _, %v)", n, err, expectedErrWithoutBcastOpt)
}
})
}
}