Add support for the experiment option header in IPv6.

PiperOrigin-RevId: 702771719
This commit is contained in:
Lucas Manning
2024-12-04 10:25:30 -08:00
committed by gVisor bot
parent b78f2ee7c4
commit cd70b0a4c0
9 changed files with 238 additions and 5 deletions
+14
View File
@@ -1882,6 +1882,20 @@ func IPv6HopByHopExtensionHeader(checkers ...IPv6ExtHdrOptionChecker) IPv6ExtHdr
}
}
// IPv6ExperimentHeader checks the extension header is an Experiment extension
// header and validates the value.
func IPv6ExperimentHeader(want uint16) IPv6ExtHdrChecker {
return func(t *testing.T, payloadHeader header.IPv6PayloadHeader) {
h, ok := payloadHeader.(header.IPv6ExperimentExtHdr)
if !ok {
t.Errorf("got = %T, want = header.IPv6UnknownExtHdrOption", payloadHeader)
}
if h.Value != want {
t.Errorf("got = %d, want = %d", h.Value, want)
}
}
}
// IPv6RouterAlert validates that an extension header option is the RouterAlert
// option and matches on its value.
func IPv6RouterAlert(want header.IPv6RouterAlertValue) IPv6ExtHdrOptionChecker {
+57 -1
View File
@@ -49,6 +49,10 @@ const (
// of an IPv6 payload, as per RFC 8200 section 4.7.
IPv6NoNextHeaderIdentifier IPv6ExtensionHeaderIdentifier = 59
// IPv6ExperimentExtHdrIdentifier is the header identifier of an Experiment
// extension header, as per RFC 4727 section 3.3.
IPv6ExperimentExtHdrIdentifier IPv6ExtensionHeaderIdentifier = 253
// IPv6UnknownExtHdrIdentifier is reserved by IANA.
// https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml#extension-header
// "254 Use for experimentation and testing [RFC3692][RFC4727]"
@@ -411,6 +415,17 @@ type IPv6DestinationOptionsExtHdr struct {
// isIPv6PayloadHeader implements IPv6PayloadHeader.isIPv6PayloadHeader.
func (IPv6DestinationOptionsExtHdr) isIPv6PayloadHeader() {}
// IPv6ExperimentExtHdr is a buffer holding the Experiment extension header.
type IPv6ExperimentExtHdr struct {
Value uint16
}
// Release implements IPv6PayloadHeader.Release.
func (IPv6ExperimentExtHdr) Release() {}
// isIPv6PayloadHeader implements IPv6PayloadHeader.isIPv6PayloadHeader.
func (IPv6ExperimentExtHdr) isIPv6PayloadHeader() {}
// IPv6RoutingExtHdr is a buffer holding the Routing extension header specific
// data as outlined in RFC 8200 section 4.4.
type IPv6RoutingExtHdr struct {
@@ -625,6 +640,17 @@ func (i *IPv6PayloadIterator) Next() (IPv6PayloadHeader, bool, error) {
i.nextHdrIdentifier = nextHdrIdentifier
return IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr{view}}, false, nil
case IPv6ExperimentExtHdrIdentifier:
var data [IPv6ExperimentHdrLength - ipv6ExperimentHdrValueOffset]byte
nextHdrIdentifier, _, err := i.nextHeaderData(false /* fragmentHdr */, data[:])
if err != nil {
return nil, true, err
}
i.nextHdrIdentifier = nextHdrIdentifier
hdr := IPv6ExperimentExtHdr{
Value: binary.BigEndian.Uint16(data[:ipv6ExperimentHdrTagLength]),
}
return hdr, false, nil
case IPv6NoNextHeaderIdentifier:
// This indicates the end of the IPv6 payload.
return nil, true, nil
@@ -689,7 +715,7 @@ func (i *IPv6PayloadIterator) nextHeaderData(fragmentHdr bool, bytes []byte) (IP
i.nextOffset += uint32((length + 1) * ipv6ExtHdrLenBytesPerUnit)
bytesLen := int(length)*ipv6ExtHdrLenBytesPerUnit + ipv6ExtHdrLenBytesExcluded
if fragmentHdr {
if bytes != nil {
if n := len(bytes); n < bytesLen {
panic(fmt.Sprintf("bytes only has space for %d bytes but need space for %d bytes (length = %d) for extension header with id = %d", n, bytesLen, length, i.nextHdrIdentifier))
}
@@ -735,6 +761,36 @@ type IPv6SerializableExtHdr interface {
serializeInto(nextHeader uint8, b []byte) int
}
// ipv6RouterAlertPayloadLength is the length of the Router Alert payload
// as defined in RFC 4727 section 3.3.
const (
IPv6ExperimentHdrLength = 8
ipv6ExperimentNextHeaderOffset = 0
ipv6ExperimentLengthOffset = 1
ipv6ExperimentHdrValueOffset = 2
ipv6ExperimentHdrTagLength = 2
)
var _ IPv6SerializableExtHdr = (*IPv6ExperimentExtHdr)(nil)
// identifier implements IPv6SerializableExtHdr.
func (h IPv6ExperimentExtHdr) identifier() IPv6ExtensionHeaderIdentifier {
return IPv6ExperimentExtHdrIdentifier
}
// length implements IPv6SerializableExtHdr.
func (h IPv6ExperimentExtHdr) length() int {
return IPv6ExperimentHdrLength
}
// serializeInto implements IPv6SerializableExtHdr.
func (h IPv6ExperimentExtHdr) serializeInto(nextHeader uint8, b []byte) int {
b[ipv6ExperimentNextHeaderOffset] = nextHeader
b[ipv6ExperimentLengthOffset] = (IPv6ExperimentHdrLength / ipv6ExtHdrLenBytesPerUnit) - 1
binary.BigEndian.PutUint16(b[ipv6ExperimentHdrValueOffset:][:ipv6ExperimentHdrTagLength], uint16(h.Value))
return IPv6ExperimentHdrLength
}
var _ IPv6SerializableExtHdr = (*IPv6SerializableHopByHopExtHdr)(nil)
// IPv6SerializableHopByHopExtHdr implements serialization of the Hop by Hop
+4
View File
@@ -738,6 +738,9 @@ func (e *endpoint) MaxHeaderLength() uint16 {
}
func addIPHeader(srcAddr, dstAddr tcpip.Address, pkt *stack.PacketBuffer, params stack.NetworkHeaderParams, extensionHeaders header.IPv6ExtHdrSerializer) tcpip.Error {
if params.ExperimentOptionValue != 0 {
extensionHeaders = append(extensionHeaders, &header.IPv6ExperimentExtHdr{Value: params.ExperimentOptionValue})
}
extHdrsLen := extensionHeaders.Length()
length := pkt.Size() + extensionHeaders.Length()
if length > math.MaxUint16 {
@@ -1463,6 +1466,7 @@ func (e *endpoint) processExtensionHeader(it *header.IPv6PayloadIterator, pkt **
if err := e.processIPv6RawPayloadHeader(&extHdr, it, *pkt, *routerAlert, previousHeaderStart, *hasFragmentHeader); err != nil {
return true, err
}
case header.IPv6ExperimentExtHdr:
default:
// Since the iterator returns IPv6RawPayloadHeader for unknown Extension
// Header IDs this should never happen unless we missed a supported type
+1
View File
@@ -450,6 +450,7 @@ func (gd *GRO) dispatch6(pkt *stack.PacketBuffer) {
case header.IPv6HopByHopOptionsExtHdr:
case header.IPv6RoutingExtHdr:
case header.IPv6DestinationOptionsExtHdr:
case header.IPv6ExperimentExtHdr:
default:
// This is either a TCP header or something we can't handle.
ipHdrSize = int(it.HeaderOffset())
@@ -310,6 +310,13 @@ func (c *WriteContext) newPacketBufferLocked(reserveHdrBytes int, data buffer.Bu
// This matches Linux behaviour:
// https://github.com/torvalds/linux/blob/38d741cb70b/include/net/sock.h#L2519
// https://github.com/torvalds/linux/blob/38d741cb70b/net/core/sock.c#L2588
var expOptVal uint16
if nic, err := c.e.stack.GetNICByID(c.route.OutgoingNIC()); err == nil && nic.ExperimentIPOptionEnabled() {
expOptVal = c.e.ops.GetExperimentOptionValue()
}
if c.route.NetProto() == header.IPv6ProtocolNumber && expOptVal != 0 {
reserveHdrBytes += header.IPv6ExperimentHdrLength
}
pktSize := int64(reserveHdrBytes) + int64(data.Size())
e.sendBufferSizeInUse += pktSize
+14 -2
View File
@@ -820,7 +820,11 @@ type tcpFields struct {
func (e *Endpoint) sendSynTCP(r *stack.Route, tf tcpFields, opts header.TCPSynOptions) tcpip.Error {
tf.opts = makeSynOptions(opts)
// We ignore SYN send errors and let the callers re-attempt send.
p := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: header.TCPMinimumSize + int(r.MaxHeaderLength()) + len(tf.opts)})
hdrSize := header.TCPMinimumSize + int(r.MaxHeaderLength()) + len(tf.opts)
if r.NetProto() == header.IPv6ProtocolNumber && tf.expOptVal != 0 {
hdrSize += header.IPv6ExperimentHdrLength
}
p := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: hdrSize})
defer p.DecRef()
if err := e.sendTCP(r, tf, p, stack.GSO{}); err != nil {
e.stats.SendErrors.SynSendToNetworkFailed.Increment()
@@ -892,6 +896,10 @@ func sendTCPBatch(r *stack.Route, tf tcpFields, pkt *stack.PacketBuffer, gso sta
// packet already has the truncated data.
shouldSplitPacket := i != n-1
if shouldSplitPacket {
if r.NetProto() == header.IPv6ProtocolNumber && tf.expOptVal != 0 {
// Reserve extra bytes for the experiment option.
hdrSize += header.IPv6ExperimentHdrLength
}
splitPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: hdrSize})
splitPkt.Data().ReadFromPacketData(pkt.Data(), packetSize)
pkt = splitPkt
@@ -1018,7 +1026,11 @@ func (e *Endpoint) sendRaw(pkt *stack.PacketBuffer, flags header.TCPFlags, seq,
}
options := e.makeOptions(sackBlocks)
defer putOptions(options)
pkt.ReserveHeaderBytes(header.TCPMinimumSize + int(e.route.MaxHeaderLength()) + len(options))
hdrSize := header.TCPMinimumSize + int(e.route.MaxHeaderLength()) + len(options)
if e.route.NetProto() == header.IPv6ProtocolNumber && e.getExperimentOptionValue(e.route) != 0 {
hdrSize += header.IPv6ExperimentHdrLength
}
pkt.ReserveHeaderBytes(hdrSize)
return e.sendTCP(e.route, tcpFields{
id: e.TransportEndpointInfo.ID,
ttl: calculateTTL(e.route, e.ipv4TTL, e.ipv6HopLimit),
+7 -2
View File
@@ -233,12 +233,17 @@ func replyWithReset(st *stack.Stack, s *segment, tos, ipv4TTL uint8, ipv6HopLimi
ack = s.sequenceNumber.Add(s.logicalLen())
}
p := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: header.TCPMinimumSize + int(route.MaxHeaderLength())})
defer p.DecRef()
var expOptVal uint16
if s.ep != nil {
expOptVal = s.ep.getExperimentOptionValue(route)
}
hdrSize := header.TCPMinimumSize + int(route.MaxHeaderLength())
if route.NetProto() == header.IPv6ProtocolNumber && expOptVal != 0 {
hdrSize += header.IPv6ExperimentHdrLength
}
p := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: hdrSize})
defer p.DecRef()
return sendTCP(route, tcpFields{
id: s.id,
ttl: ttl,
@@ -9452,6 +9452,104 @@ func TestSetExperimentOption(t *testing.T) {
checker.IPv4(t, v, checker.IPv4Options(want))
}
func TestSetExperimentOptionIPv6(t *testing.T) {
c := context.NewWithOpts(t, context.Options{
EnableV4: false,
EnableV6: true,
MTU: e2e.DefaultMTU,
EnableExperimentIPOption: true,
})
defer c.Cleanup()
ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &c.WQ)
if err != nil {
t.Fatalf("NewEndpoint failed: %v", err)
}
c.EP = ep
// Start connection attempt.
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.WritableEvents)
c.WQ.EventRegister(&waitEntry)
defer c.WQ.EventUnregister(&waitEntry)
err = c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort})
if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
t.Fatalf("Unexpected return value from Connect: %v", err)
}
// Receive SYN packet.
b := c.GetV6Packet()
defer b.Release()
checker.IPv6(t, b,
checker.TCP(
checker.DstPort(context.TestPort),
checker.TCPFlags(header.TCPFlagSyn),
),
)
if got, want := tcp.EndpointState(c.EP.State()), tcp.StateSynSent; got != want {
t.Fatalf("Unexpected endpoint state: want %v, got %v", want, got)
}
iss := seqnum.Value(context.TestInitialSequenceNumber)
rcvWnd := seqnum.Size(30000)
tcpHdr := header.TCP(header.IPv6(b.AsSlice()).Payload())
synOpts := header.ParseSynOptions(tcpHdr.Options(), false /* isAck */)
c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
c.SendV6Packet(nil, &context.Headers{
SrcPort: tcpHdr.DestinationPort(),
DstPort: tcpHdr.SourcePort(),
Flags: header.TCPFlagSyn | header.TCPFlagAck,
SeqNum: iss,
AckNum: c.IRS.Add(1),
RcvWnd: rcvWnd,
TCPOpts: nil,
})
// Receive ACK packet.
b = c.GetV6Packet()
defer b.Release()
checker.IPv6(t, b,
checker.TCP(
checker.DstPort(context.TestPort),
checker.TCPFlags(header.TCPFlagAck),
checker.TCPSeqNum(uint32(c.IRS)+1),
checker.TCPAckNum(uint32(iss)+1),
),
)
// Wait for connection to be established.
select {
case <-notifyCh:
if err := c.EP.LastError(); err != nil {
t.Fatalf("Unexpected error when connecting: %v", err)
}
case <-time.After(1 * time.Second):
t.Fatalf("Timed out waiting for connection")
}
if got, want := tcp.EndpointState(c.EP.State()), tcp.StateEstablished; got != want {
t.Fatalf("Unexpected endpoint state: want %v, got %v", want, got)
}
c.RcvdWindowScale = uint8(synOpts.WS)
c.Port = tcpHdr.SourcePort()
var expval uint16 = 99
c.EP.SocketOptions().SetExperimentOptionValue(expval)
var r bytes.Reader
r.Reset(make([]byte, 1))
_, err = c.EP.Write(&r, tcpip.WriteOptions{})
if err != nil {
t.Fatalf("Write failed: %s", err)
}
v := c.GetV6Packet()
defer v.Release()
checker.IPv6WithExtHdr(t, v, checker.IPv6ExtHdr(checker.IPv6ExperimentHeader(expval)))
}
func TestSetExperimentOptionWithOptionDisabled(t *testing.T) {
c := context.NewWithOpts(t, context.Options{
EnableV4: true,
+36
View File
@@ -2336,6 +2336,42 @@ func TestSetExperimentOption(t *testing.T) {
checker.IPv4(t, v, checker.IPv4Options(want))
}
func TestSetExperimentOptionIPv6(t *testing.T) {
opts := context.Options{
EnableExperimentIPOption: true,
MTU: context.DefaultMTU,
HandleLocal: true,
}
c := context.NewWithOptions(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}, opts)
defer c.Cleanup()
c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber)
if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort}); err != nil {
c.T.Fatalf("Connect failed: %s", err)
}
var expval uint16 = 99
c.EP.SocketOptions().SetExperimentOptionValue(expval)
var r bytes.Reader
r.Reset(make([]byte, 1))
_, err := c.EP.Write(&r, tcpip.WriteOptions{})
if err != nil {
t.Fatalf("Write failed: %s", err)
}
pkt := c.LinkEP.Read()
if pkt == nil {
t.Fatal("Packet wasn't written out")
}
defer pkt.DecRef()
v := stack.PayloadSince(pkt.LinkHeader())
defer v.Release()
checker.IPv6WithExtHdr(t, v, checker.IPv6ExtHdr(checker.IPv6ExperimentHeader(expval)))
}
func TestMain(m *testing.M) {
refs.SetLeakMode(refs.LeaksPanic)
code := m.Run()