diff options
author | Julian Elischer <jrelis@google.com> | 2020-11-11 10:57:32 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-11-11 10:59:35 -0800 |
commit | 9c4102896d8ffbe6a90b57e7aca85f912dcadd9c (patch) | |
tree | c6a5bac4968e0fa260ff4bb5a60e5c9d55f2335c /pkg | |
parent | 792cbc06de41f226f76f55a828dfcfad9b8fb16e (diff) |
Teach netstack how to add options to IPv4 packets
Most packets don't have options but they are an integral part of the
standard. Teaching the ipv4 code how to handle them will simplify future
testing and use. Because Options are so rare it is worth making sure
that the extra work is kept out of the fast path as much as possible.
Prior to this change, all usages of the IHL field of the IPv4Fields/Encode
system set it to the same constant value except in a couple of tests
for bad values. From this change IHL will not be a constant as it will
depend on the size of any Options. Since ipv4.Encode() now handles the
options it becomes a possible source of errors to let the callers set
this value, so remove it entirely and calculate the value from the size
of the Options if present (or not) therefore guaranteeing a correct value.
Fixes #4709
RELNOTES: n/a
PiperOrigin-RevId: 341864765
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/tcpip/checker/checker.go | 4 | ||||
-rw-r--r-- | pkg/tcpip/header/ipv4.go | 45 | ||||
-rw-r--r-- | pkg/tcpip/header/ipversion_test.go | 2 | ||||
-rw-r--r-- | pkg/tcpip/network/ip_test.go | 29 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4.go | 19 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4_test.go | 130 | ||||
-rw-r--r-- | pkg/tcpip/stack/registration.go | 19 | ||||
-rw-r--r-- | pkg/tcpip/stack/transport_demuxer_test.go | 1 | ||||
-rw-r--r-- | pkg/tcpip/tests/integration/multicast_broadcast_test.go | 2 | ||||
-rw-r--r-- | pkg/tcpip/transport/tcp/testing/context/context.go | 2 | ||||
-rw-r--r-- | pkg/tcpip/transport/udp/udp_test.go | 1 |
11 files changed, 135 insertions, 119 deletions
diff --git a/pkg/tcpip/checker/checker.go b/pkg/tcpip/checker/checker.go index 530f2ae2f..8868cf4e3 100644 --- a/pkg/tcpip/checker/checker.go +++ b/pkg/tcpip/checker/checker.go @@ -197,7 +197,7 @@ func IPPayload(payload []byte) NetworkChecker { } // IPv4Options returns a checker that checks the options in an IPv4 packet. -func IPv4Options(want []byte) NetworkChecker { +func IPv4Options(want header.IPv4Options) NetworkChecker { return func(t *testing.T, h []header.Network) { t.Helper() @@ -205,7 +205,7 @@ func IPv4Options(want []byte) NetworkChecker { if !ok { t.Fatalf("unexpected network header passed to checker, got = %T, want = header.IPv4", h[0]) } - options := []byte(ip.Options()) + options := ip.Options() // cmp.Diff does not consider nil slices equal to empty slices, but we do. if len(want) == 0 && len(options) == 0 { return diff --git a/pkg/tcpip/header/ipv4.go b/pkg/tcpip/header/ipv4.go index 961b77628..7e32b31b4 100644 --- a/pkg/tcpip/header/ipv4.go +++ b/pkg/tcpip/header/ipv4.go @@ -56,12 +56,9 @@ const ( ) // IPv4Fields contains the fields of an IPv4 packet. It is used to describe the -// fields of a packet that needs to be encoded. +// fields of a packet that needs to be encoded. The IHL field is not here as +// it is totally defined by the size of the options. type IPv4Fields struct { - // IHL is the "internet header length" field of an IPv4 packet. The value - // is in bytes. - IHL uint8 - // TOS is the "type of service" field of an IPv4 packet. TOS uint8 @@ -91,6 +88,9 @@ type IPv4Fields struct { // DstAddr is the "destination ip address" of an IPv4 packet. DstAddr tcpip.Address + + // Options is between 0 and 40 bytes or nil if empty. + Options IPv4Options } // IPv4 is an IPv4 header. @@ -118,7 +118,7 @@ const ( // Linux limits this to 65,515 octets (the max IP datagram size - the IPv4 // header size). But RFC 791 section 3.2 discusses the design of the IPv4 // fragment "allows 2**13 = 8192 fragments of 8 octets each for a total of - // 65,536 octets. Note that this is consistent with the the datagram total + // 65,536 octets. Note that this is consistent with the datagram total // length field (of course, the header is counted in the total length and not // in the fragments)." IPv4MaximumPayloadSize = 65536 @@ -275,10 +275,22 @@ func (b IPv4) DestinationAddress() tcpip.Address { // IPv4Options is a buffer that holds all the raw IP options. type IPv4Options []byte -// Options returns a buffer holding the options. +// AllocationSize implements stack.NetOptions. +// It reports the size to allocate for the Options. RFC 791 page 23 (end of +// section 3.1) says of the padding at the end of the options: +// The internet header padding is used to ensure that the internet +// header ends on a 32 bit boundary. +func (o IPv4Options) AllocationSize() int { + return (len(o) + IPv4IHLStride - 1) & ^(IPv4IHLStride - 1) +} + +// Options returns a buffer holding the options or nil. func (b IPv4) Options() IPv4Options { hdrLen := b.HeaderLength() - return IPv4Options(b[options:hdrLen:hdrLen]) + if hdrLen > IPv4MinimumSize { + return IPv4Options(b[options:hdrLen:hdrLen]) + } + return nil } // TransportProtocol implements Network.TransportProtocol. @@ -351,7 +363,22 @@ func (b IPv4) CalculateChecksum() uint16 { // Encode encodes all the fields of the IPv4 header. func (b IPv4) Encode(i *IPv4Fields) { - b.SetHeaderLength(i.IHL) + // The size of the options defines the size of the whole header and thus the + // IHL field. Options are rare and this is a heavily used function so it is + // worth a bit of optimisation here to keep the copy out of the fast path. + hdrLen := IPv4MinimumSize + if len(i.Options) != 0 { + // AllocationSize is always >= len(i.Options). + aLen := i.Options.AllocationSize() + hdrLen += aLen + if hdrLen > len(b) { + panic(fmt.Sprintf("encode received %d bytes, wanted >= %d", len(b), hdrLen)) + } + if aLen != copy(b[options:], i.Options) { + _ = copy(b[options+len(i.Options):options+aLen], []byte{0, 0, 0, 0}) + } + } + b.SetHeaderLength(uint8(hdrLen)) b[tos] = i.TOS b.SetTotalLength(i.TotalLength) binary.BigEndian.PutUint16(b[id:], i.ID) diff --git a/pkg/tcpip/header/ipversion_test.go b/pkg/tcpip/header/ipversion_test.go index 17a49d4fa..b5540bf66 100644 --- a/pkg/tcpip/header/ipversion_test.go +++ b/pkg/tcpip/header/ipversion_test.go @@ -22,7 +22,7 @@ import ( func TestIPv4(t *testing.T) { b := header.IPv4(make([]byte, header.IPv4MinimumSize)) - b.Encode(&header.IPv4Fields{IHL: header.IPv4MinimumSize}) + b.Encode(&header.IPv4Fields{}) const want = header.IPv4Version if v := header.IPVersion(b); v != want { diff --git a/pkg/tcpip/network/ip_test.go b/pkg/tcpip/network/ip_test.go index 8873bd91f..c7d26e14f 100644 --- a/pkg/tcpip/network/ip_test.go +++ b/pkg/tcpip/network/ip_test.go @@ -320,7 +320,6 @@ func TestSourceAddressValidation(t *testing.T) { pkt.SetChecksum(^header.Checksum(pkt, 0)) ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(totalLen), Protocol: uint8(icmp.ProtocolNumber4), TTL: ipv4.DefaultTTL, @@ -579,7 +578,6 @@ func TestIPv4Receive(t *testing.T) { view := buffer.NewView(totalLen) ip := header.IPv4(view) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(totalLen), TTL: 20, Protocol: 10, @@ -662,7 +660,6 @@ func TestIPv4ReceiveControl(t *testing.T) { // Create the outer IPv4 header. ip := header.IPv4(view) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(len(view) - c.trunc), TTL: 20, Protocol: uint8(header.ICMPv4ProtocolNumber), @@ -681,7 +678,6 @@ func TestIPv4ReceiveControl(t *testing.T) { // Create the inner IPv4 header. ip = header.IPv4(view[header.IPv4MinimumSize+header.ICMPv4MinimumSize:]) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: 100, TTL: 20, Protocol: 10, @@ -740,7 +736,6 @@ func TestIPv4FragmentationReceive(t *testing.T) { frag1 := buffer.NewView(totalLen) ip1 := header.IPv4(frag1) ip1.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(totalLen), TTL: 20, Protocol: 10, @@ -759,7 +754,6 @@ func TestIPv4FragmentationReceive(t *testing.T) { frag2 := buffer.NewView(totalLen) ip2 := header.IPv4(frag2) ip2.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(totalLen), TTL: 20, Protocol: 10, @@ -1052,15 +1046,13 @@ func TestWriteHeaderIncludedPacket(t *testing.T) { nicID = 1 transportProto = 5 - dataLen = 4 - optionsLen = 4 + dataLen = 4 ) dataBuf := [dataLen]byte{1, 2, 3, 4} data := dataBuf[:] - ipv4OptionsBuf := [optionsLen]byte{0, 1, 0, 1} - ipv4Options := ipv4OptionsBuf[:] + ipv4Options := header.IPv4Options{0, 1, 0, 1} ipv6FragmentExtHdrBuf := [header.IPv6FragmentExtHdrLength]byte{transportProto, 0, 62, 4, 1, 2, 3, 4} ipv6FragmentExtHdr := ipv6FragmentExtHdrBuf[:] @@ -1098,7 +1090,6 @@ func TestWriteHeaderIncludedPacket(t *testing.T) { } ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, Protocol: transportProto, TTL: ipv4.DefaultTTL, SrcAddr: src, @@ -1140,12 +1131,12 @@ func TestWriteHeaderIncludedPacket(t *testing.T) { } ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize - 1, Protocol: transportProto, TTL: ipv4.DefaultTTL, SrcAddr: src, DstAddr: header.IPv4Any, }) + ip.SetHeaderLength(header.IPv4MinimumSize - 1) return hdr.View().ToVectorisedView() }, expectedErr: tcpip.ErrMalformedHeader, @@ -1159,7 +1150,6 @@ func TestWriteHeaderIncludedPacket(t *testing.T) { pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView { ip := header.IPv4(make([]byte, header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, Protocol: transportProto, TTL: ipv4.DefaultTTL, SrcAddr: src, @@ -1178,7 +1168,6 @@ func TestWriteHeaderIncludedPacket(t *testing.T) { pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView { ip := header.IPv4(make([]byte, header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, Protocol: transportProto, TTL: ipv4.DefaultTTL, SrcAddr: src, @@ -1213,7 +1202,7 @@ func TestWriteHeaderIncludedPacket(t *testing.T) { nicAddr: localIPv4Addr, remoteAddr: remoteIPv4Addr, pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView { - ipHdrLen := header.IPv4MinimumSize + len(ipv4Options) + ipHdrLen := header.IPv4MinimumSize + ipv4Options.AllocationSize() totalLen := ipHdrLen + len(data) hdr := buffer.NewPrependable(totalLen) if n := copy(hdr.Prepend(len(data)), data); n != len(data) { @@ -1221,15 +1210,12 @@ func TestWriteHeaderIncludedPacket(t *testing.T) { } ip := header.IPv4(hdr.Prepend(ipHdrLen)) ip.Encode(&header.IPv4Fields{ - IHL: uint8(ipHdrLen), Protocol: transportProto, TTL: ipv4.DefaultTTL, SrcAddr: src, DstAddr: header.IPv4Any, + Options: ipv4Options, }) - if n := copy(ip.Options(), ipv4Options); n != len(ipv4Options) { - t.Fatalf("copied %d bytes, expected %d bytes", n, len(ipv4Options)) - } return hdr.View().ToVectorisedView() }, checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) { @@ -1261,16 +1247,15 @@ func TestWriteHeaderIncludedPacket(t *testing.T) { nicAddr: localIPv4Addr, remoteAddr: remoteIPv4Addr, pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView { - ip := header.IPv4(make([]byte, header.IPv4MinimumSize)) + ip := header.IPv4(make([]byte, header.IPv4MinimumSize+ipv4Options.AllocationSize())) ip.Encode(&header.IPv4Fields{ - IHL: uint8(header.IPv4MinimumSize + len(ipv4Options)), Protocol: transportProto, TTL: ipv4.DefaultTTL, SrcAddr: src, DstAddr: header.IPv4Any, + Options: ipv4Options, }) vv := buffer.View(ip).ToVectorisedView() - vv.AppendView(ipv4Options) vv.AppendView(data) return vv }, diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index cfd0c505a..a376cb8ec 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -199,14 +199,28 @@ func (e *endpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber { } func (e *endpoint) addIPHeader(r *stack.Route, pkt *stack.PacketBuffer, params stack.NetworkHeaderParams) { - ip := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize)) + hdrLen := header.IPv4MinimumSize + var opts header.IPv4Options + if params.Options != nil { + var ok bool + if opts, ok = params.Options.(header.IPv4Options); !ok { + panic(fmt.Sprintf("want IPv4Options, got %T", params.Options)) + } + hdrLen += opts.AllocationSize() + if hdrLen > header.IPv4MaximumHeaderSize { + // Since we have no way to report an error we must either panic or create + // a packet which is different to what was requested. Choose panic as this + // would be a programming error that should be caught in testing. + panic(fmt.Sprintf("IPv4 Options %d bytes, Max %d", params.Options.AllocationSize(), header.IPv4MaximumOptionsSize)) + } + } + ip := header.IPv4(pkt.NetworkHeader().Push(hdrLen)) length := uint16(pkt.Size()) // RFC 6864 section 4.3 mandates uniqueness of ID values for non-atomic // datagrams. Since the DF bit is never being set here, all datagrams // are non-atomic and need an ID. id := atomic.AddUint32(&e.protocol.ids[hashRoute(r, params.Protocol, e.protocol.hashIV)%buckets], 1) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: length, ID: uint16(id), TTL: params.TTL, @@ -214,6 +228,7 @@ func (e *endpoint) addIPHeader(r *stack.Route, pkt *stack.PacketBuffer, params s Protocol: uint8(params.Protocol), SrcAddr: r.LocalAddress, DstAddr: r.RemoteAddress, + Options: opts, }) ip.SetChecksum(^ip.CalculateChecksum()) pkt.NetworkProtocolNumber = ProtocolNumber diff --git a/pkg/tcpip/network/ipv4/ipv4_test.go b/pkg/tcpip/network/ipv4/ipv4_test.go index c7f434591..c6e565455 100644 --- a/pkg/tcpip/network/ipv4/ipv4_test.go +++ b/pkg/tcpip/network/ipv4/ipv4_test.go @@ -127,8 +127,8 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength uint16 transportProtocol uint8 TTL uint8 - options []byte - replyOptions []byte // if succeeds, reply should look like this + options header.IPv4Options + replyOptions header.IPv4Options // reply should look like this shouldFail bool expectErrorICMP bool ICMPType header.ICMPv4Type @@ -177,24 +177,24 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{0, 0, 0, 0}, - replyOptions: []byte{0, 0, 0, 0}, + options: header.IPv4Options{0, 0, 0, 0}, + replyOptions: header.IPv4Options{0, 0, 0, 0}, }, { name: "NOP options", maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{1, 1, 1, 1}, - replyOptions: []byte{1, 1, 1, 1}, + options: header.IPv4Options{1, 1, 1, 1}, + replyOptions: header.IPv4Options{1, 1, 1, 1}, }, { name: "NOP and End options", maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{1, 1, 0, 0}, - replyOptions: []byte{1, 1, 0, 0}, + options: header.IPv4Options{1, 1, 0, 0}, + replyOptions: header.IPv4Options{1, 1, 0, 0}, }, { name: "bad header length", @@ -240,12 +240,12 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 12, 13, 0x11, 192, 168, 1, 12, 1, 2, 3, 4, }, - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 68, 12, 13, 0x21, 192, 168, 1, 12, 1, 2, 3, 4, @@ -256,7 +256,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 12, 13, 0xF1, // ^ Counter full (15/0xF) 192, 168, 1, 12, @@ -267,24 +267,24 @@ func TestIPv4Sanity(t *testing.T) { ICMPType: header.ICMPv4ParamProblem, ICMPCode: header.ICMPv4UnusedCode, paramProblemPointer: header.IPv4MinimumSize + 3, - replyOptions: []byte{}, + replyOptions: header.IPv4Options{}, }, { name: "unknown option", maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{10, 4, 9, 0}, + options: header.IPv4Options{10, 4, 9, 0}, // ^^ // The unknown option should be stripped out of the reply. - replyOptions: []byte{}, + replyOptions: header.IPv4Options{}, }, { name: "bad option - length 0", maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 0, 9, 0, // ^ 1, 2, 3, 4, @@ -296,7 +296,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 9, 9, 0, // ^ // There are only 8 bytes allocated to options so 9 bytes of timestamp @@ -315,7 +315,7 @@ func TestIPv4Sanity(t *testing.T) { TTL: ttl, // Timestamps are in multiples of 4 or 8 but never 7. // The option space should be padded out. - options: []byte{ + options: header.IPv4Options{ 68, 7, 5, 0, // ^ ^ Linux points here which is wrong. // | Not a multiple of 4 @@ -332,7 +332,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 24, 21, 0x00, 1, 2, 3, 4, 5, 6, 7, 8, @@ -340,7 +340,7 @@ func TestIPv4Sanity(t *testing.T) { 13, 14, 15, 16, 0, 0, 0, 0, }, - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 68, 24, 25, 0x00, 1, 2, 3, 4, 5, 6, 7, 8, @@ -355,7 +355,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 20, 21, 0x11, // ^ 192, 168, 1, 12, @@ -364,7 +364,7 @@ func TestIPv4Sanity(t *testing.T) { 5, 6, 7, 8, }, // Overflow count is the top nibble of the 4th byte. - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 68, 20, 21, 0x21, // ^ 192, 168, 1, 12, @@ -378,7 +378,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 28, 21, 0x01, 192, 168, 1, 12, 1, 2, 3, 4, @@ -387,7 +387,7 @@ func TestIPv4Sanity(t *testing.T) { 0, 0, 0, 0, 0, 0, 0, 0, }, - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 68, 28, 29, 0x01, 192, 168, 1, 12, 1, 2, 3, 4, @@ -403,7 +403,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 20, 17, 0x01, // ^^ ^^ 20 byte area, next free spot at 17. 192, 168, 1, 12, @@ -423,13 +423,13 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 68, 12, 13, 0x11, 192, 168, 1, 12, 1, 2, 3, 4, 0, 10, 3, 99, }, - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 68, 12, 13, 0x21, 192, 168, 1, 12, 1, 2, 3, 4, @@ -442,7 +442,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{68, 1, 0, 0}, + options: header.IPv4Options{68, 1, 0, 0}, // ^ Smallest possible is 8. shouldFail: true, }, @@ -451,12 +451,12 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 7, 7, 4, // 3 byte header 0, 0, 0, 0, 0, }, - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 7, 7, 8, // 3 byte header 192, 168, 1, 58, // New IP Address. 0, // padding to multiple of 4 bytes. @@ -467,7 +467,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 7, 23, 20, // 3 byte header 1, 2, 3, 4, 5, 6, 7, 8, @@ -476,7 +476,7 @@ func TestIPv4Sanity(t *testing.T) { 0, 0, 0, 0, 0, }, - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 7, 23, 24, 1, 2, 3, 4, 5, 6, 7, 8, @@ -491,12 +491,12 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 7, 7, 8, // 3 byte header 1, 2, 3, 4, 0, }, - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 7, 7, 8, // 3 byte header 1, 2, 3, 4, 0, // padding to multiple of 4 bytes. @@ -508,7 +508,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 7, 23, 24, // 3 byte header 1, 2, 3, 4, 5, 6, 7, 8, @@ -517,7 +517,7 @@ func TestIPv4Sanity(t *testing.T) { 17, 18, 19, 20, 0, }, - replyOptions: []byte{ + replyOptions: header.IPv4Options{ 7, 23, 24, 1, 2, 3, 4, 5, 6, 7, 8, @@ -534,7 +534,7 @@ func TestIPv4Sanity(t *testing.T) { maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 7, 8, 8, // 3 byte header // ^ ^ Linux points here. We must too. // | Not enough room. 1 byte free, need 4. @@ -546,14 +546,14 @@ func TestIPv4Sanity(t *testing.T) { ICMPType: header.ICMPv4ParamProblem, ICMPCode: header.ICMPv4UnusedCode, paramProblemPointer: header.IPv4MinimumSize + 2, - replyOptions: []byte{}, + replyOptions: header.IPv4Options{}, }, { name: "duplicate record route", maxTotalLength: ipv4.MaxTotalSize, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - options: []byte{ + options: header.IPv4Options{ 7, 7, 8, // 3 byte header 1, 2, 3, 4, 7, 7, 8, // 3 byte header @@ -565,7 +565,7 @@ func TestIPv4Sanity(t *testing.T) { ICMPType: header.ICMPv4ParamProblem, ICMPCode: header.ICMPv4UnusedCode, paramProblemPointer: header.IPv4MinimumSize + 7, - replyOptions: []byte{}, + replyOptions: header.IPv4Options{}, }, } @@ -599,12 +599,7 @@ func TestIPv4Sanity(t *testing.T) { }, }) - // Round up the header size to the next multiple of 4 as RFC 791, page 11 - // says: "Internet Header Length is the length of the internet header - // in 32 bit words..." and on page 23: "The internet header padding is - // used to ensure that the internet header ends on a 32 bit boundary." - ipHeaderLength := ((header.IPv4MinimumSize + len(test.options)) + header.IPv4IHLStride - 1) & ^(header.IPv4IHLStride - 1) - + ipHeaderLength := header.IPv4MinimumSize + test.options.AllocationSize() if ipHeaderLength > header.IPv4MaximumHeaderSize { t.Fatalf("too many bytes in options: got = %d, want <= %d ", ipHeaderLength, header.IPv4MaximumHeaderSize) } @@ -624,17 +619,13 @@ func TestIPv4Sanity(t *testing.T) { totalLen = test.maxTotalLength } ip.Encode(&header.IPv4Fields{ - IHL: uint8(ipHeaderLength), TotalLength: totalLen, Protocol: test.transportProtocol, TTL: test.TTL, SrcAddr: remoteIPv4Addr, DstAddr: ipv4Addr.Address, + Options: test.options, }) - if n := copy(ip.Options(), test.options); n != len(test.options) { - t.Fatalf("options larger than available space: copied %d/%d bytes", n, len(test.options)) - } - // Override the correct value if the test case specified one. if test.headerLength != 0 { ip.SetHeaderLength(test.headerLength) } @@ -1160,9 +1151,11 @@ func TestInvalidFragments(t *testing.T) { } type fragmentData struct { - ipv4fields header.IPv4Fields + ipv4fields header.IPv4Fields + // 0 means insert the correct IHL. Non 0 means override the correct IHL. + overrideIHL int // For 0 use 1 as it is an int and will be divided by 4. payload []byte - autoChecksum bool // if true, the Checksum field will be overwritten. + autoChecksum bool // If true, the Checksum field will be overwritten. } tests := []struct { @@ -1176,7 +1169,6 @@ func TestInvalidFragments(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: 0, TOS: tos, TotalLength: 0, ID: ident, @@ -1187,6 +1179,7 @@ func TestInvalidFragments(t *testing.T) { SrcAddr: addr1, DstAddr: addr2, }, + overrideIHL: 1, // See note above. payload: payloadGen(12), autoChecksum: true, }, @@ -1199,7 +1192,6 @@ func TestInvalidFragments(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: 0, TOS: tos, TotalLength: 0, ID: ident, @@ -1210,6 +1202,7 @@ func TestInvalidFragments(t *testing.T) { SrcAddr: addr1, DstAddr: addr2, }, + overrideIHL: 1, // See note above. payload: payloadGen(12), autoChecksum: true, }, @@ -1224,7 +1217,6 @@ func TestInvalidFragments(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 17, ID: ident, @@ -1249,7 +1241,6 @@ func TestInvalidFragments(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 16, ID: ident, @@ -1272,7 +1263,6 @@ func TestInvalidFragments(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize - 12, TOS: tos, TotalLength: header.IPv4MinimumSize + 28, ID: ident, @@ -1284,11 +1274,11 @@ func TestInvalidFragments(t *testing.T) { DstAddr: addr2, }, payload: payloadGen(28), + overrideIHL: header.IPv4MinimumSize - 12, autoChecksum: true, }, { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize - 12, TOS: tos, TotalLength: header.IPv4MinimumSize - 12, ID: ident, @@ -1300,6 +1290,7 @@ func TestInvalidFragments(t *testing.T) { DstAddr: addr2, }, payload: payloadGen(28), + overrideIHL: header.IPv4MinimumSize - 12, autoChecksum: true, }, }, @@ -1311,7 +1302,6 @@ func TestInvalidFragments(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize + 4, TOS: tos, TotalLength: header.IPv4MinimumSize + 28, ID: ident, @@ -1323,11 +1313,11 @@ func TestInvalidFragments(t *testing.T) { DstAddr: addr2, }, payload: payloadGen(28), + overrideIHL: header.IPv4MinimumSize + 4, autoChecksum: true, }, { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize + 4, TOS: tos, TotalLength: header.IPv4MinimumSize + 4, ID: ident, @@ -1339,6 +1329,7 @@ func TestInvalidFragments(t *testing.T) { DstAddr: addr2, }, payload: payloadGen(28), + overrideIHL: header.IPv4MinimumSize + 4, autoChecksum: true, }, }, @@ -1350,7 +1341,6 @@ func TestInvalidFragments(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 8, ID: ident, @@ -1366,7 +1356,6 @@ func TestInvalidFragments(t *testing.T) { }, { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 8, ID: ident, @@ -1382,7 +1371,6 @@ func TestInvalidFragments(t *testing.T) { }, { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 8, ID: ident, @@ -1423,6 +1411,11 @@ func TestInvalidFragments(t *testing.T) { ip := header.IPv4(hdr.Prepend(pktSize)) ip.Encode(&f.ipv4fields) + // Encode sets this up correctly. If we want a different value for + // testing then we need to overwrite the good value. + if f.overrideIHL != 0 { + ip.SetHeaderLength(uint8(f.overrideIHL)) + } copy(ip[header.IPv4MinimumSize:], f.payload) if f.autoChecksum { @@ -1474,7 +1467,6 @@ func TestFragmentReassemblyTimeout(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 16, ID: ident, @@ -1495,7 +1487,6 @@ func TestFragmentReassemblyTimeout(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 16, ID: ident, @@ -1510,7 +1501,6 @@ func TestFragmentReassemblyTimeout(t *testing.T) { }, { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 16, ID: ident, @@ -1531,7 +1521,6 @@ func TestFragmentReassemblyTimeout(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16), ID: ident, @@ -1552,7 +1541,6 @@ func TestFragmentReassemblyTimeout(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 8, ID: ident, @@ -1567,7 +1555,6 @@ func TestFragmentReassemblyTimeout(t *testing.T) { }, { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16), ID: ident, @@ -1588,7 +1575,6 @@ func TestFragmentReassemblyTimeout(t *testing.T) { fragments: []fragmentData{ { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16), ID: ident, @@ -1603,7 +1589,6 @@ func TestFragmentReassemblyTimeout(t *testing.T) { }, { ipv4fields: header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: tos, TotalLength: header.IPv4MinimumSize + 8, ID: ident, @@ -2104,7 +2089,6 @@ func TestReceiveFragments(t *testing.T) { // Serialize IPv4 fixed header. ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: header.IPv4MinimumSize + uint16(len(frag.payload)), ID: frag.id, Flags: frag.flags, @@ -2370,7 +2354,6 @@ func TestPacketQueing(t *testing.T) { u.SetChecksum(^u.CalculateChecksum(sum)) ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: header.IPv4MinimumSize + header.UDPMinimumSize, TTL: ipv4.DefaultTTL, Protocol: uint8(udp.ProtocolNumber), @@ -2414,7 +2397,6 @@ func TestPacketQueing(t *testing.T) { pkt.SetChecksum(^header.Checksum(pkt, 0)) ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(totalLen), Protocol: uint8(icmp.ProtocolNumber4), TTL: ipv4.DefaultTTL, diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index b8f333057..00e9a82ae 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -138,7 +138,7 @@ type PacketEndpoint interface { HandlePacket(nicID tcpip.NICID, addr tcpip.LinkAddress, netProto tcpip.NetworkProtocolNumber, pkt *PacketBuffer) } -// UnknownDestinationPacketDisposition enumerates the possible return vaues from +// UnknownDestinationPacketDisposition enumerates the possible return values from // HandleUnknownDestinationPacket(). type UnknownDestinationPacketDisposition int @@ -263,6 +263,15 @@ const ( PacketLoop ) +// NetOptions is an interface that allows us to pass network protocol specific +// options through the Stack layer code. +type NetOptions interface { + // AllocationSize returns the amount of memory that must be allocated to + // hold the options given that the value must be rounded up to the next + // multiple of 4 bytes. + AllocationSize() int +} + // NetworkHeaderParams are the header parameters given as input by the // transport endpoint to the network. type NetworkHeaderParams struct { @@ -274,6 +283,10 @@ type NetworkHeaderParams struct { // TOS refers to TypeOfService or TrafficClass field of the IP-header. TOS uint8 + + // Options is a set of options to add to a network header (or nil). + // It will be protocol specific opaque information from higher layers. + Options NetOptions } // GroupAddressableEndpoint is an endpoint that supports group addressing. @@ -281,7 +294,7 @@ type NetworkHeaderParams struct { // An endpoint is considered to support group addressing when one or more // endpoints may associate themselves with the same identifier (group address). type GroupAddressableEndpoint interface { - // JoinGroup joins the spcified group. + // JoinGroup joins the specified group. // // Returns true if the group was newly joined. JoinGroup(group tcpip.Address) (bool, *tcpip.Error) @@ -378,7 +391,7 @@ type AddressEndpoint interface { SetDeprecated(bool) } -// AddressKind is the kind of of an address. +// AddressKind is the kind of an address. // // See the values of AddressKind for more details. type AddressKind int diff --git a/pkg/tcpip/stack/transport_demuxer_test.go b/pkg/tcpip/stack/transport_demuxer_test.go index 698c8609e..41a8e5ad0 100644 --- a/pkg/tcpip/stack/transport_demuxer_test.go +++ b/pkg/tcpip/stack/transport_demuxer_test.go @@ -102,7 +102,6 @@ func (c *testContext) sendV4Packet(payload []byte, h *headers, linkEpID tcpip.NI // Initialize the IP header. ip := header.IPv4(buf) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: 0x80, TotalLength: uint16(len(buf)), TTL: 65, diff --git a/pkg/tcpip/tests/integration/multicast_broadcast_test.go b/pkg/tcpip/tests/integration/multicast_broadcast_test.go index cdf0459e3..1eecd7957 100644 --- a/pkg/tcpip/tests/integration/multicast_broadcast_test.go +++ b/pkg/tcpip/tests/integration/multicast_broadcast_test.go @@ -73,7 +73,6 @@ func TestPingMulticastBroadcast(t *testing.T) { pkt.SetChecksum(^header.Checksum(pkt, 0)) ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(totalLen), Protocol: uint8(icmp.ProtocolNumber4), TTL: ttl, @@ -244,7 +243,6 @@ func TestIncomingMulticastAndBroadcast(t *testing.T) { ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize)) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(totalLen), Protocol: uint8(udp.ProtocolNumber), TTL: ttl, diff --git a/pkg/tcpip/transport/tcp/testing/context/context.go b/pkg/tcpip/transport/tcp/testing/context/context.go index f791f8f13..e6aa4fc4b 100644 --- a/pkg/tcpip/transport/tcp/testing/context/context.go +++ b/pkg/tcpip/transport/tcp/testing/context/context.go @@ -395,7 +395,6 @@ func (c *Context) SendICMPPacket(typ header.ICMPv4Type, code header.ICMPv4Code, ip := header.IPv4(buf) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(len(buf)), TTL: 65, Protocol: uint8(header.ICMPv4ProtocolNumber), @@ -437,7 +436,6 @@ func (c *Context) BuildSegmentWithAddrs(payload []byte, h *Headers, src, dst tcp // Initialize the IP header. ip := header.IPv4(buf) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TotalLength: uint16(len(buf)), TTL: 65, Protocol: uint8(tcp.ProtocolNumber), diff --git a/pkg/tcpip/transport/udp/udp_test.go b/pkg/tcpip/transport/udp/udp_test.go index fb7738dda..c09c7aa86 100644 --- a/pkg/tcpip/transport/udp/udp_test.go +++ b/pkg/tcpip/transport/udp/udp_test.go @@ -490,7 +490,6 @@ func (c *testContext) buildV4Packet(payload []byte, h *header4Tuple) buffer.View // Initialize the IP header. ip := header.IPv4(buf) ip.Encode(&header.IPv4Fields{ - IHL: header.IPv4MinimumSize, TOS: testTOS, TotalLength: uint16(len(buf)), TTL: 65, |