mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add support for the experiment option header in IPv6.
PiperOrigin-RevId: 702771719
This commit is contained in:
committed by
gVisor bot
parent
b78f2ee7c4
commit
cd70b0a4c0
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user