Skip to content

Commit

Permalink
Add support for setting the experiment option header in IPv4.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 702486039
  • Loading branch information
manninglucas authored and gvisor-bot committed Dec 3, 2024
1 parent 9eb188d commit 078be62
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 12 deletions.
65 changes: 65 additions & 0 deletions pkg/tcpip/header/ipv4.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,9 @@ const (
// IPv4OptionTimestampType is the option type for the Timestamp option.
IPv4OptionTimestampType IPv4OptionType = 68

// IPv4OptionExperimentType is the option type for the Experiment option.
IPv4OptionExperimentType IPv4OptionType = 30

// ipv4OptionTypeOffset is the offset in an option of its type field.
ipv4OptionTypeOffset = 0

Expand Down Expand Up @@ -800,6 +803,17 @@ func (i *IPv4OptionIterator) Next() (IPv4Option, bool, *IPv4OptParameterProblem)
}
retval := IPv4OptionRouterAlert(optionBody)
return &retval, false, nil

case IPv4OptionExperimentType:
if optLen != IPv4OptionExperimentLength {
i.ErrCursor++
return nil, false, &IPv4OptParameterProblem{
Pointer: i.ErrCursor,
NeedICMP: true,
}
}
retval := IPv4OptionExperiment(optionBody)
return &retval, false, nil
}
retval := IPv4OptionGeneric(optionBody)
return &retval, false, nil
Expand Down Expand Up @@ -1074,6 +1088,35 @@ func (ra *IPv4OptionRouterAlert) Value() uint16 {
return binary.BigEndian.Uint16(ra.Contents()[IPv4OptionRouterAlertValueOffset:])
}

// Experiment option specific related constants.
const (
// IPv4OptionExperimentLength is the length of an Experiment option.
IPv4OptionExperimentLength = 4

// IPv4OptionExperimentValueOffset is the offset for the value of an
// Experiment option.
IPv4OptionExperimentValueOffset = 2
)

var _ IPv4Option = (*IPv4OptionExperiment)(nil)

// IPv4OptionExperiment is an IPv4 option defined by RFC 4727.
type IPv4OptionExperiment []byte

// Type implements IPv4Option.
func (*IPv4OptionExperiment) Type() IPv4OptionType { return IPv4OptionExperimentType }

// Size implements IPv4Option.
func (*IPv4OptionExperiment) Size() uint8 { return uint8(IPv4OptionExperimentLength) }

// Contents implements IPv4Option.
func (ex *IPv4OptionExperiment) Contents() []byte { return *ex }

// Value returns the value of the IPv4OptionRouterAlert.
func (ex *IPv4OptionExperiment) Value() uint16 {
return binary.BigEndian.Uint16(ex.Contents()[IPv4OptionExperimentValueOffset:])
}

// IPv4SerializableOption is an interface to represent serializable IPv4 option
// types.
type IPv4SerializableOption interface {
Expand Down Expand Up @@ -1179,6 +1222,28 @@ func (o *IPv4SerializableRouterAlertOption) serializeInto(buffer []byte) uint8 {
return o.length()
}

var _ IPv4SerializableOptionPayload = (*IPv4SerializableExperimentOption)(nil)
var _ IPv4SerializableOption = (*IPv4SerializableExperimentOption)(nil)

// IPv4SerializableExperimentOption provides serialization for the IPv4
// Experiment option.
type IPv4SerializableExperimentOption struct {
Tag uint16
}

func (*IPv4SerializableExperimentOption) optionType() IPv4OptionType {
return IPv4OptionExperimentType
}

func (*IPv4SerializableExperimentOption) length() uint8 {
return IPv4OptionExperimentLength - IPv4OptionExperimentValueOffset
}

func (o *IPv4SerializableExperimentOption) serializeInto(buffer []byte) uint8 {
binary.BigEndian.PutUint16(buffer, o.Tag)
return o.length()
}

var _ IPv4SerializableOption = (*IPv4SerializableNOPOption)(nil)

// IPv4SerializableNOPOption provides serialization for the IPv4 no-op option.
Expand Down
3 changes: 3 additions & 0 deletions pkg/tcpip/network/ipv4/ipv4.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,9 @@ func (e *endpoint) getID() uint16 {
}

func (e *endpoint) addIPHeader(srcAddr, dstAddr tcpip.Address, pkt *stack.PacketBuffer, params stack.NetworkHeaderParams, options header.IPv4OptionsSerializer) tcpip.Error {
if expVal := params.ExperimentOptionValue; expVal != 0 {
options = append(options, &header.IPv4SerializableExperimentOption{Tag: expVal})
}
hdrLen := header.IPv4MinimumSize
var optLen int
if options != nil {
Expand Down
19 changes: 10 additions & 9 deletions pkg/tcpip/transport/tcp/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -1020,15 +1020,16 @@ func (e *Endpoint) sendRaw(pkt *stack.PacketBuffer, flags header.TCPFlags, seq,
defer putOptions(options)
pkt.ReserveHeaderBytes(header.TCPMinimumSize + int(e.route.MaxHeaderLength()) + len(options))
return e.sendTCP(e.route, tcpFields{
id: e.TransportEndpointInfo.ID,
ttl: calculateTTL(e.route, e.ipv4TTL, e.ipv6HopLimit),
tos: e.sendTOS,
flags: flags,
seq: seq,
ack: ack,
rcvWnd: rcvWnd,
opts: options,
df: e.pmtud == tcpip.PMTUDiscoveryWant || e.pmtud == tcpip.PMTUDiscoveryDo,
id: e.TransportEndpointInfo.ID,
ttl: calculateTTL(e.route, e.ipv4TTL, e.ipv6HopLimit),
tos: e.sendTOS,
flags: flags,
seq: seq,
ack: ack,
rcvWnd: rcvWnd,
opts: options,
df: e.pmtud == tcpip.PMTUDiscoveryWant || e.pmtud == tcpip.PMTUDiscoveryDo,
expOptVal: e.getExperimentOptionValue(e.route),
}, pkt, e.gso)
}

Expand Down
57 changes: 57 additions & 0 deletions pkg/tcpip/transport/tcp/test/e2e/tcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9421,6 +9421,63 @@ func TestLateSynCookieAck(t *testing.T) {
}
}

func TestSetExperimentOption(t *testing.T) {
c := context.NewWithOpts(t, context.Options{
EnableV4: true,
MTU: e2e.DefaultMTU,
EnableExperimentIPOption: true,
})
defer c.Cleanup()

c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)

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.GetPacket()
defer v.Release()
want := header.IPv4Options{
byte(header.IPv4OptionExperimentType),
byte(header.IPv4OptionExperimentLength),
0,
byte(expval),
}
checker.IPv4(t, v, checker.IPv4Options(want))
}

func TestSetExperimentOptionWithOptionDisabled(t *testing.T) {
c := context.NewWithOpts(t, context.Options{
EnableV4: true,
MTU: e2e.DefaultMTU,
EnableExperimentIPOption: false,
})
defer c.Cleanup()

c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)

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.GetPacket()
defer v.Release()
want := header.IPv4Options{}
checker.IPv4(t, v, checker.IPv4Options(want))
}

func TestMain(m *testing.M) {
refs.SetLeakMode(refs.LeaksPanic)
code := m.Run()
Expand Down
8 changes: 6 additions & 2 deletions pkg/tcpip/transport/tcp/testing/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ type Options struct {

// Probe is a probe function to attach to the stack.
Probe tcp.TCPProbeFunc

// EnableExperimentIPOption indicates whether the NIC is responsible for
// passing the experiment IP option.
EnableExperimentIPOption bool
}

// Context provides an initialized Network stack and a link layer endpoint
Expand Down Expand Up @@ -247,15 +251,15 @@ func NewWithOpts(t *testing.T, opts Options) *Context {
if testing.Verbose() {
wep = sniffer.New(ep)
}
nicOpts := stack.NICOptions{Name: "nic1"}
nicOpts := stack.NICOptions{Name: "nic1", EnableExperimentIPOption: opts.EnableExperimentIPOption}
if err := s.CreateNICWithOptions(1, wep, nicOpts); err != nil {
t.Fatalf("CreateNICWithOptions(_, _, %+v) failed: %v", opts, err)
}
wep2 := stack.LinkEndpoint(channel.New(1000, opts.MTU, ""))
if testing.Verbose() {
wep2 = sniffer.New(channel.New(1000, opts.MTU, ""))
}
opts2 := stack.NICOptions{Name: "nic2"}
opts2 := stack.NICOptions{Name: "nic2", EnableExperimentIPOption: opts.EnableExperimentIPOption}
if err := s.CreateNICWithOptions(2, wep2, opts2); err != nil {
t.Fatalf("CreateNICWithOptions(_, _, %+v) failed: %v", opts2, err)
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/tcpip/transport/testing/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ type Options struct {
// HandleLocal specifies if non-loopback interfaces are allowed to loop
// packets.
HandleLocal bool

// EnableExperimentIPOption indicates whether the NIC is responsible for
// passing the experiment IP option.
EnableExperimentIPOption bool
}

// New allocates and initializes a test context containing a configured stack.
Expand Down Expand Up @@ -112,7 +116,7 @@ func NewWithOptions(t *testing.T, transportProtocols []stack.TransportProtocolFa
if testing.Verbose() {
wep = sniffer.New(ep)
}
if err := s.CreateNIC(NICID, wep); err != nil {
if err := s.CreateNICWithOptions(NICID, wep, stack.NICOptions{Name: "nic1", EnableExperimentIPOption: options.EnableExperimentIPOption}); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", NICID, err)
}

Expand Down
43 changes: 43 additions & 0 deletions pkg/tcpip/transport/udp/udp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2293,6 +2293,49 @@ func TestWritePayloadSizeTooBig(t *testing.T) {
}
}

func TestSetExperimentOption(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(ipv4.ProtocolNumber, udp.ProtocolNumber)

if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestAddr, 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)
}

want := header.IPv4Options{
byte(header.IPv4OptionExperimentType),
byte(header.IPv4OptionExperimentLength),
0,
byte(expval),
}

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.IPv4(t, v, checker.IPv4Options(want))
}

func TestMain(m *testing.M) {
refs.SetLeakMode(refs.LeaksPanic)
code := m.Run()
Expand Down

0 comments on commit 078be62

Please sign in to comment.