Use protocol-specific options for TTL/HopLimit

The new HopLimit matches the IPV6_UNICAST_HOPS socket option.

Updates #6389

PiperOrigin-RevId: 418831844
This commit is contained in:
Arthur Sfez
2021-12-29 12:33:48 -08:00
committed by gVisor bot
parent 108885b9e3
commit 58b9bdfc21
13 changed files with 427 additions and 69 deletions
+34 -2
View File
@@ -1351,6 +1351,26 @@ func getSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name
v := primitive.Int32(boolToInt32(ep.SocketOptions().GetV6Only()))
return &v, nil
case linux.IPV6_UNICAST_HOPS:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
}
v, err := ep.GetSockOptInt(tcpip.IPv6HopLimitOption)
if err != nil {
return nil, syserr.TranslateNetstackError(err)
}
// Fill in the default value, if needed.
vP := primitive.Int32(v)
if vP == -1 {
// TODO(https://github.com/google/gvisor/issues/6973): Retrieve the
// configured DefaultTTLOption of the IPv6 protocol.
vP = DefaultTTL
}
return &vP, nil
case linux.IPV6_PATHMTU:
t.Kernel().EmitUnimplementedEvent(t)
@@ -1499,7 +1519,7 @@ func getSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in
return nil, syserr.ErrInvalidArgument
}
v, err := ep.GetSockOptInt(tcpip.TTLOption)
v, err := ep.GetSockOptInt(tcpip.IPv4TTLOption)
if err != nil {
return nil, syserr.TranslateNetstackError(err)
}
@@ -1507,6 +1527,8 @@ func getSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in
// Fill in the default value, if needed.
vP := primitive.Int32(v)
if vP == 0 {
// TODO(https://github.com/google/gvisor/issues/6973): Retrieve the
// configured DefaultTTLOption of the IPv4 protocol.
vP = DefaultTTL
}
@@ -2200,6 +2222,16 @@ func setSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name
ep.SocketOptions().SetIPv6ReceivePacketInfo(v != 0)
return nil
case linux.IPV6_UNICAST_HOPS:
if len(optVal) < sizeOfInt32 {
return syserr.ErrInvalidArgument
}
v := int32(hostarch.ByteOrder.Uint32(optVal))
if v < -1 || v > 255 {
return syserr.ErrInvalidArgument
}
return syserr.TranslateNetstackError(ep.SetSockOptInt(tcpip.IPv6HopLimitOption, int(v)))
case linux.IPV6_TCLASS:
if len(optVal) < sizeOfInt32 {
return syserr.ErrInvalidArgument
@@ -2410,7 +2442,7 @@ func setSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in
} else if v < 1 || v > 255 {
return syserr.ErrInvalidArgument
}
return syserr.TranslateNetstackError(ep.SetSockOptInt(tcpip.TTLOption, int(v)))
return syserr.TranslateNetstackError(ep.SetSockOptInt(tcpip.IPv4TTLOption, int(v)))
case linux.IP_TOS:
if len(optVal) == 0 {
+24 -5
View File
@@ -731,12 +731,19 @@ const (
// number of unread bytes in the output buffer should be returned.
SendQueueSizeOption
// TTLOption is used by SetSockOptInt/GetSockOptInt to control the
// default TTL/hop limit value for unicast messages. The default is
// protocol specific.
// IPv4TTLOption is used by SetSockOptInt/GetSockOptInt to control the default
// TTL value for unicast messages.
//
// A zero value indicates the default.
TTLOption
// The default is configured by DefaultTTLOption. A UseDefaultIPv4TTL value
// configures the endpoint to use the default.
IPv4TTLOption
// IPv6HopLimitOption is used by SetSockOptInt/GetSockOptInt to control the
// default hop limit value for unicast messages.
//
// The default is configured by DefaultTTLOption. A UseDefaultIPv6HopLimit
// value configures the endpoint to use the default.
IPv6HopLimitOption
// TCPSynCountOption is used by SetSockOptInt/GetSockOptInt to specify
// the number of SYN retransmits that TCP should send before aborting
@@ -752,6 +759,18 @@ const (
TCPWindowClampOption
)
const (
// UseDefaultIPv4TTL is the IPv4TTLOption value that configures an endpoint to
// use the default ttl currently configured by the IPv4 protocol (see
// DefaultTTLOption).
UseDefaultIPv4TTL = 0
// UseDefaultIPv6HopLimit is the IPv6HopLimitOption value that configures an
// endpoint to use the default hop limit currently configured by the IPv6
// protocol (see DefaultTTLOption).
UseDefaultIPv6HopLimit = -1
)
const (
// PMTUDiscoveryWant is a setting of the MTUDiscoverOption to use
// per-route settings.
@@ -53,9 +53,10 @@ type Endpoint struct {
connectedRoute *stack.Route `state:"manual"`
// +checklocks:mu
multicastMemberships map[multicastMembership]struct{}
// TODO(https://gvisor.dev/issue/6389): Use different fields for IPv4/IPv6.
// +checklocks:mu
ttl uint8
ipv4TTL uint8
// +checklocks:mu
ipv6HopLimit int16
// TODO(https://gvisor.dev/issue/6389): Use different fields for IPv4/IPv6.
// +checklocks:mu
multicastTTL uint8
@@ -131,6 +132,8 @@ func (e *Endpoint) Init(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, tr
TransProto: transProto,
},
effectiveNetProto: netProto,
ipv4TTL: tcpip.UseDefaultIPv4TTL,
ipv6HopLimit: tcpip.UseDefaultIPv6HopLimit,
// Linux defaults to TTL=1.
multicastTTL: 1,
multicastMemberships: make(map[multicastMembership]struct{}),
@@ -191,16 +194,27 @@ func (e *Endpoint) SetOwner(owner tcpip.PacketOwner) {
e.owner = owner
}
func calculateTTL(route *stack.Route, ttl uint8, multicastTTL uint8) uint8 {
if header.IsV4MulticastAddress(route.RemoteAddress()) || header.IsV6MulticastAddress(route.RemoteAddress()) {
return multicastTTL
// +checklocksread:e.mu
func (e *Endpoint) calculateTTL(route *stack.Route) uint8 {
remoteAddress := route.RemoteAddress()
if header.IsV4MulticastAddress(remoteAddress) || header.IsV6MulticastAddress(remoteAddress) {
return e.multicastTTL
}
if ttl == 0 {
return route.DefaultTTL()
switch netProto := route.NetProto(); netProto {
case header.IPv4ProtocolNumber:
if e.ipv4TTL == 0 {
return route.DefaultTTL()
}
return e.ipv4TTL
case header.IPv6ProtocolNumber:
if e.ipv6HopLimit == -1 {
return route.DefaultTTL()
}
return uint8(e.ipv6HopLimit)
default:
panic(fmt.Sprintf("invalid protocol number = %d", netProto))
}
return ttl
}
// WriteContext holds the context for a write.
@@ -327,7 +341,7 @@ func (e *Endpoint) AcquireContextForWrite(opts tcpip.WriteOptions) (WriteContext
return WriteContext{
transProto: e.transProto,
route: route,
ttl: calculateTTL(route, e.ttl, e.multicastTTL),
ttl: e.calculateTTL(route),
tos: tos,
owner: e.owner,
}, nil
@@ -602,9 +616,14 @@ func (e *Endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
e.multicastTTL = uint8(v)
e.mu.Unlock()
case tcpip.TTLOption:
case tcpip.IPv4TTLOption:
e.mu.Lock()
e.ttl = uint8(v)
e.ipv4TTL = uint8(v)
e.mu.Unlock()
case tcpip.IPv6HopLimitOption:
e.mu.Lock()
e.ipv6HopLimit = int16(v)
e.mu.Unlock()
case tcpip.IPv4TOSOption:
@@ -634,9 +653,15 @@ func (e *Endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
e.mu.Unlock()
return v, nil
case tcpip.TTLOption:
case tcpip.IPv4TTLOption:
e.mu.Lock()
v := int(e.ttl)
v := int(e.ipv4TTL)
e.mu.Unlock()
return v, nil
case tcpip.IPv6HopLimitOption:
e.mu.Lock()
v := int(e.ipv6HopLimit)
e.mu.Unlock()
return v, nil
+3 -3
View File
@@ -435,7 +435,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) tcpip.Err
// RFC 793 section 3.4 page 35 (figure 12) outlines that a RST
// must be sent in response to a SYN-ACK while in the listen
// state to prevent completing a handshake from an old SYN.
return replyWithReset(e.stack, s, e.sendTOS, e.ttl)
return replyWithReset(e.stack, s, e.sendTOS, e.ipv4TTL, e.ipv6HopLimit)
}
switch {
@@ -569,7 +569,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) tcpip.Err
cookie := ctx.createCookie(s.id, s.sequenceNumber, encodeMSS(opts.MSS))
fields := tcpFields{
id: s.id,
ttl: e.ttl,
ttl: calculateTTL(route, e.ipv4TTL, e.ipv6HopLimit),
tos: e.sendTOS,
flags: header.TCPFlagSyn | header.TCPFlagAck,
seq: cookie,
@@ -616,7 +616,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) tcpip.Err
// The only time we should reach here when a connection
// was opened and closed really quickly and a delayed
// ACK was received from the sender.
return replyWithReset(e.stack, s, e.sendTOS, e.ttl)
return replyWithReset(e.stack, s, e.sendTOS, e.ipv4TTL, e.ipv6HopLimit)
}
// Keep hold of acceptMu until the new endpoint is in the accept queue (or
+6 -12
View File
@@ -283,7 +283,7 @@ func (h *handshake) synSentState(s *segment) tcpip.Error {
// but resend our own SYN and wait for it to be acknowledged in the
// SYN-RCVD state.
h.state = handshakeSynRcvd
ttl := h.ep.ttl
ttl := calculateTTL(h.ep.route, h.ep.ipv4TTL, h.ep.ipv6HopLimit)
amss := h.ep.amss
h.ep.setEndpointState(StateSynRecv)
synOpts := header.TCPSynOptions{
@@ -366,7 +366,7 @@ func (h *handshake) synRcvdState(s *segment) tcpip.Error {
}
h.ep.sendSynTCP(h.ep.route, tcpFields{
id: h.ep.TransportEndpointInfo.ID,
ttl: h.ep.ttl,
ttl: calculateTTL(h.ep.route, h.ep.ipv4TTL, h.ep.ipv6HopLimit),
tos: h.ep.sendTOS,
flags: h.flags,
seq: h.iss,
@@ -508,7 +508,7 @@ func (h *handshake) start() {
h.sendSYNOpts = synOpts
h.ep.sendSynTCP(h.ep.route, tcpFields{
id: h.ep.TransportEndpointInfo.ID,
ttl: h.ep.ttl,
ttl: calculateTTL(h.ep.route, h.ep.ipv4TTL, h.ep.ipv6HopLimit),
tos: h.ep.sendTOS,
flags: h.flags,
seq: h.iss,
@@ -556,7 +556,7 @@ func (h *handshake) complete() tcpip.Error {
if h.active || !h.acked || h.deferAccept != 0 && h.ep.stack.Clock().NowMonotonic().Sub(h.startTime) > h.deferAccept {
h.ep.sendSynTCP(h.ep.route, tcpFields{
id: h.ep.TransportEndpointInfo.ID,
ttl: h.ep.ttl,
ttl: calculateTTL(h.ep.route, h.ep.ipv4TTL, h.ep.ipv6HopLimit),
tos: h.ep.sendTOS,
flags: h.flags,
seq: h.iss,
@@ -847,9 +847,6 @@ func sendTCPBatch(r *stack.Route, tf tcpFields, data buffer.VectorisedView, gso
buildTCPHdr(r, tf, pkt, gso)
tf.seq = tf.seq.Add(seqnum.Size(packetSize))
pkt.GSOOptions = gso
if tf.ttl == 0 {
tf.ttl = r.DefaultTTL()
}
if err := r.WritePacket(stack.NetworkHeaderParams{Protocol: ProtocolNumber, TTL: tf.ttl, TOS: tf.tos}, pkt); err != nil {
r.Stats().TCP.SegmentSendErrors.Increment()
pkt.DecRef()
@@ -883,9 +880,6 @@ func sendTCP(r *stack.Route, tf tcpFields, data buffer.VectorisedView, gso stack
pkt.Owner = owner
buildTCPHdr(r, tf, pkt, gso)
if tf.ttl == 0 {
tf.ttl = r.DefaultTTL()
}
if err := r.WritePacket(stack.NetworkHeaderParams{Protocol: ProtocolNumber, TTL: tf.ttl, TOS: tf.tos}, pkt); err != nil {
r.Stats().TCP.SegmentSendErrors.Increment()
return err
@@ -945,7 +939,7 @@ func (e *endpoint) sendRaw(data buffer.VectorisedView, flags header.TCPFlags, se
options := e.makeOptions(sackBlocks)
err := e.sendTCP(e.route, tcpFields{
id: e.TransportEndpointInfo.ID,
ttl: e.ttl,
ttl: calculateTTL(e.route, e.ipv4TTL, e.ipv6HopLimit),
tos: e.sendTOS,
flags: flags,
seq: seq,
@@ -1049,7 +1043,7 @@ func (e *endpoint) tryDeliverSegmentFromClosedEndpoint(s *segment) {
)
}
if ep == nil {
replyWithReset(e.stack, s, stack.DefaultTOS, 0 /* ttl */)
replyWithReset(e.stack, s, stack.DefaultTOS, tcpip.UseDefaultIPv4TTL, tcpip.UseDefaultIPv6HopLimit)
s.decRef()
return
}
+38 -5
View File
@@ -436,7 +436,8 @@ type endpoint struct {
isRegistered bool `state:"manual"`
boundNICID tcpip.NICID
route *stack.Route `state:"manual"`
ttl uint8
ipv4TTL uint8
ipv6HopLimit int16
isConnectNotified bool
// h stores a reference to the current handshake state if the endpoint is in
@@ -783,6 +784,25 @@ func (e *endpoint) recentTimestamp() uint32 {
return e.RecentTS
}
// TODO(gvisor.dev/issue/6974): Remove once tcp endpoints are composed with a
// network.Endpoint, which also defines this function.
func calculateTTL(route *stack.Route, ipv4TTL uint8, ipv6HopLimit int16) uint8 {
switch netProto := route.NetProto(); netProto {
case header.IPv4ProtocolNumber:
if ipv4TTL == tcpip.UseDefaultIPv4TTL {
return route.DefaultTTL()
}
return ipv4TTL
case header.IPv6ProtocolNumber:
if ipv6HopLimit == tcpip.UseDefaultIPv6HopLimit {
return route.DefaultTTL()
}
return uint8(ipv6HopLimit)
default:
panic(fmt.Sprintf("invalid protocol number = %d", netProto))
}
}
// keepalive is a synchronization wrapper used to appease stateify. See the
// comment in endpoint, where it is used.
//
@@ -818,6 +838,8 @@ func newEndpoint(s *stack.Stack, protocol *protocol, netProto tcpip.NetworkProto
count: DefaultKeepaliveCount,
},
uniqueID: s.UniqueID(),
ipv4TTL: tcpip.UseDefaultIPv4TTL,
ipv6HopLimit: tcpip.UseDefaultIPv6HopLimit,
txHash: s.Rand().Uint32(),
windowClamp: DefaultReceiveBufferSize,
maxSynRetries: DefaultSynRetries,
@@ -1775,9 +1797,14 @@ func (e *endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
return &tcpip.ErrNotSupported{}
}
case tcpip.TTLOption:
case tcpip.IPv4TTLOption:
e.LockUser()
e.ttl = uint8(v)
e.ipv4TTL = uint8(v)
e.UnlockUser()
case tcpip.IPv6HopLimitOption:
e.LockUser()
e.ipv6HopLimit = int16(v)
e.UnlockUser()
case tcpip.TCPSynCountOption:
@@ -1960,9 +1987,15 @@ func (e *endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
case tcpip.ReceiveQueueSizeOption:
return e.readyReceiveSize()
case tcpip.TTLOption:
case tcpip.IPv4TTLOption:
e.LockUser()
v := int(e.ttl)
v := int(e.ipv4TTL)
e.UnlockUser()
return v, nil
case tcpip.IPv6HopLimitOption:
e.LockUser()
v := int(e.ipv6HopLimit)
e.UnlockUser()
return v, nil
+1 -1
View File
@@ -132,7 +132,7 @@ func (r *ForwarderRequest) Complete(sendReset bool) {
r.forwarder.mu.Unlock()
if sendReset {
replyWithReset(r.forwarder.stack, r.segment, stack.DefaultTOS, 0 /* ttl */)
replyWithReset(r.forwarder.stack, r.segment, stack.DefaultTOS, tcpip.UseDefaultIPv4TTL, tcpip.UseDefaultIPv6HopLimit)
}
// Release all resources.
+6 -7
View File
@@ -165,7 +165,7 @@ func (p *protocol) HandleUnknownDestinationPacket(id stack.TransportEndpointID,
}
if !s.flags.Contains(header.TCPFlagRst) {
replyWithReset(p.stack, s, stack.DefaultTOS, 0)
replyWithReset(p.stack, s, stack.DefaultTOS, tcpip.UseDefaultIPv4TTL, tcpip.UseDefaultIPv6HopLimit)
}
return stack.UnknownDestinationPacketHandled
@@ -191,14 +191,17 @@ func (p *protocol) tsOffset(src, dst tcpip.Address) tcp.TSOffset {
// replyWithReset replies to the given segment with a reset segment.
//
// If the passed TTL is 0, then the route's default TTL will be used.
func replyWithReset(st *stack.Stack, s *segment, tos, ttl uint8) tcpip.Error {
// If the relevant TTL has its reset value (0 for ipv4TTL, -1 for ipv6HopLimit),
// then the route's default TTL will be used.
func replyWithReset(st *stack.Stack, s *segment, tos, ipv4TTL uint8, ipv6HopLimit int16) tcpip.Error {
route, err := st.FindRoute(s.nicID, s.dstAddr, s.srcAddr, s.netProto, false /* multicastLoop */)
if err != nil {
return err
}
defer route.Release()
ttl := calculateTTL(route, ipv4TTL, ipv6HopLimit)
// Get the seqnum from the packet if the ack flag is set.
seq := seqnum.Value(0)
ack := seqnum.Value(0)
@@ -221,10 +224,6 @@ func replyWithReset(st *stack.Stack, s *segment, tos, ttl uint8) tcpip.Error {
ack = s.sequenceNumber.Add(s.logicalLen())
}
if ttl == 0 {
ttl = route.DefaultTTL()
}
return sendTCP(route, tcpFields{
id: s.id,
ttl: ttl,
+14 -7
View File
@@ -3463,12 +3463,14 @@ func TestDefaultTTL(t *testing.T) {
func TestSetTTL(t *testing.T) {
for _, test := range []struct {
name string
protoNum tcpip.NetworkProtocolNumber
addr tcpip.Address
name string
protoNum tcpip.NetworkProtocolNumber
addr tcpip.Address
relevantOpt tcpip.SockOptInt
irrelevantOpt tcpip.SockOptInt
}{
{"ipv4", ipv4.ProtocolNumber, context.TestAddr},
{"ipv6", ipv6.ProtocolNumber, context.TestV6Addr},
{"ipv4", ipv4.ProtocolNumber, context.TestAddr, tcpip.IPv4TTLOption, tcpip.IPv6HopLimitOption},
{"ipv6", ipv6.ProtocolNumber, context.TestV6Addr, tcpip.IPv6HopLimitOption, tcpip.IPv4TTLOption},
} {
t.Run(fmt.Sprint(test.name), func(t *testing.T) {
for _, wantTTL := range []uint8{1, 2, 50, 64, 128, 254, 255} {
@@ -3482,8 +3484,13 @@ func TestSetTTL(t *testing.T) {
t.Fatalf("NewEndpoint failed: %s", err)
}
if err := c.EP.SetSockOptInt(tcpip.TTLOption, int(wantTTL)); err != nil {
t.Fatalf("SetSockOptInt(TTLOption, %d) failed: %s", wantTTL, err)
if err := c.EP.SetSockOptInt(test.relevantOpt, int(wantTTL)); err != nil {
t.Fatalf("SetSockOptInt(%d, %d) failed: %s", test.relevantOpt, wantTTL, err)
}
// Set a different ttl/hoplimit for the unused protocol, showing that
// it does not affect the other protocol.
if err := c.EP.SetSockOptInt(test.irrelevantOpt, int(wantTTL+1)); err != nil {
t.Fatalf("SetSockOptInt(%d, %d) failed: %s", test.irrelevantOpt, wantTTL, err)
}
{
+40 -13
View File
@@ -287,13 +287,6 @@ func (flow testFlow) isReverseMulticast() bool {
}
}
func (flow testFlow) ttlOption() tcpip.SockOptInt {
if flow.isMulticast() {
return tcpip.MulticastTTLOption
}
return tcpip.TTLOption
}
type testContext struct {
t *testing.T
linkEP *channel.Endpoint
@@ -1615,7 +1608,7 @@ func (*testInterface) Enabled() bool {
return true
}
func TestNonMulticastDefaultTTL(t *testing.T) {
func TestDefaultTTL(t *testing.T) {
for _, flow := range []testFlow{unicastV4, unicastV4in6, unicastV6, unicastV6Only, broadcast, broadcastIn6} {
t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
c := newDualTestContext(t, defaultMTU)
@@ -1642,8 +1635,8 @@ func TestNonMulticastDefaultTTL(t *testing.T) {
}
}
func TestSetTTL(t *testing.T) {
for _, flow := range []testFlow{unicastV4, unicastV4in6, unicastV6, unicastV6Only, multicastV4, multicastV4in6, multicastV6, broadcast, broadcastIn6} {
func TestNonMulticastDefaultTTL(t *testing.T) {
for _, flow := range []testFlow{unicastV4, unicastV4in6, unicastV6, unicastV6Only, broadcast, broadcastIn6} {
t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
for _, wantTTL := range []uint8{1, 2, 50, 64, 128, 254, 255} {
t.Run(fmt.Sprintf("TTL:%d", wantTTL), func(t *testing.T) {
@@ -1652,9 +1645,43 @@ func TestSetTTL(t *testing.T) {
c.createEndpointForFlow(flow)
opt := flow.ttlOption()
if err := c.ep.SetSockOptInt(opt, int(wantTTL)); err != nil {
c.t.Fatalf("SetSockOptInt(%d, %d) failed: %s", opt, wantTTL, err)
var relevantOpt tcpip.SockOptInt
var irrelevantOpt tcpip.SockOptInt
if flow.isV4() {
relevantOpt = tcpip.IPv4TTLOption
irrelevantOpt = tcpip.IPv6HopLimitOption
} else {
relevantOpt = tcpip.IPv6HopLimitOption
irrelevantOpt = tcpip.IPv4TTLOption
}
if err := c.ep.SetSockOptInt(relevantOpt, int(wantTTL)); err != nil {
c.t.Fatalf("SetSockOptInt(%d, %d) failed: %s", relevantOpt, wantTTL, err)
}
// Set a different ttl/hoplimit for the unused protocol, showing that
// it does not affect the other protocol.
if err := c.ep.SetSockOptInt(irrelevantOpt, int(wantTTL+1)); err != nil {
c.t.Fatalf("SetSockOptInt(%d, %d) failed: %s", irrelevantOpt, wantTTL, err)
}
testWrite(c, flow, checker.TTL(wantTTL))
})
}
})
}
}
func TestSetMulticastTTL(t *testing.T) {
for _, flow := range []testFlow{multicastV4, multicastV4in6, multicastV6} {
t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
for _, wantTTL := range []uint8{1, 2, 50, 64, 128, 254, 255} {
t.Run(fmt.Sprintf("TTL:%d", wantTTL), func(t *testing.T) {
c := newDualTestContext(t, defaultMTU)
defer c.cleanup()
c.createEndpointForFlow(flow)
if err := c.ep.SetSockOptInt(tcpip.MulticastTTLOption, int(wantTTL)); err != nil {
c.t.Fatalf("SetSockOptInt failed: %s", err)
}
testWrite(c, flow, checker.TTL(wantTTL))
+5
View File
@@ -766,6 +766,11 @@ syscall_test(
test = "//test/syscalls/linux:socket_ip_unbound_test",
)
syscall_test(
shard_count = more_shards,
test = "//test/syscalls/linux:socket_ipv6_unbound_test",
)
syscall_test(
test = "//test/syscalls/linux:socket_ip_unbound_netlink_test",
)
+16
View File
@@ -3058,6 +3058,22 @@ cc_binary(
],
)
cc_binary(
name = "socket_ipv6_unbound_test",
testonly = 1,
srcs = [
"socket_ipv6_unbound.cc",
],
linkstatic = 1,
deps = [
":ip_socket_test_util",
"//test/util:socket_util",
gtest,
"//test/util:test_main",
"//test/util:test_util",
],
)
cc_binary(
name = "socket_ipv6_udp_unbound_loopback_test",
testonly = 1,
+201
View File
@@ -0,0 +1,201 @@
// Copyright 2021 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <netinet/in.h>
#ifdef __linux__
#include <linux/in6.h>
#endif // __linux__
#include <sys/socket.h>
#include <sys/types.h>
#include "gtest/gtest.h"
#include "test/syscalls/linux/ip_socket_test_util.h"
#include "test/util/socket_util.h"
#include "test/util/test_util.h"
namespace gvisor {
namespace testing {
namespace {
constexpr int kDefaultHopLimit = 64;
using ::testing::ValuesIn;
using IPv6UnboundSocketTest = SimpleSocketTest;
TEST_P(IPv6UnboundSocketTest, HopLimitDefault) {
std::unique_ptr<FileDescriptor> socket =
ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
int get = -1;
socklen_t get_sz = sizeof(get);
ASSERT_THAT(
getsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &get, &get_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get_sz, sizeof(get));
EXPECT_EQ(get, kDefaultHopLimit);
}
TEST_P(IPv6UnboundSocketTest, SetHopLimit) {
std::unique_ptr<FileDescriptor> socket =
ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
int get1 = -1;
socklen_t get1_sz = sizeof(get1);
ASSERT_THAT(getsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &get1,
&get1_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get1_sz, sizeof(get1));
EXPECT_EQ(get1, kDefaultHopLimit);
const int set = (get1 % 255) + 1;
ASSERT_THAT(setsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &set,
sizeof(set)),
SyscallSucceedsWithValue(0));
int get2 = -1;
socklen_t get2_sz = sizeof(get2);
ASSERT_THAT(getsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &get2,
&get2_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get2_sz, sizeof(get2));
EXPECT_EQ(get2, set);
}
TEST_P(IPv6UnboundSocketTest, ResetHopLimitToDefault) {
std::unique_ptr<FileDescriptor> socket =
ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
int get1 = -1;
socklen_t get1_sz = sizeof(get1);
ASSERT_THAT(getsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &get1,
&get1_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get1_sz, sizeof(get1));
EXPECT_EQ(get1, kDefaultHopLimit);
const int set = (get1 % 255) + 1;
ASSERT_THAT(setsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &set,
sizeof(set)),
SyscallSucceedsWithValue(0));
constexpr int kUseDefaultHopLimit = -1;
ASSERT_THAT(setsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS,
&kUseDefaultHopLimit, sizeof(kUseDefaultHopLimit)),
SyscallSucceedsWithValue(0));
int get2 = -1;
socklen_t get2_sz = sizeof(get2);
ASSERT_THAT(getsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &get2,
&get2_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get2_sz, sizeof(get2));
EXPECT_EQ(get2, get1);
}
TEST_P(IPv6UnboundSocketTest, ZeroHopLimit) {
std::unique_ptr<FileDescriptor> socket =
ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
constexpr int kZero = 0;
ASSERT_THAT(setsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kZero,
sizeof(kZero)),
SyscallSucceedsWithValue(0));
int get = -1;
socklen_t get_sz = sizeof(get);
ASSERT_THAT(
getsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &get, &get_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get, kZero);
EXPECT_EQ(get_sz, sizeof(get));
}
TEST_P(IPv6UnboundSocketTest, InvalidLargeHopLimit) {
std::unique_ptr<FileDescriptor> socket =
ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
constexpr int kInvalidLarge = 256;
EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS,
&kInvalidLarge, sizeof(kInvalidLarge)),
SyscallFailsWithErrno(EINVAL));
}
TEST_P(IPv6UnboundSocketTest, InvalidNegativeHopLimit) {
std::unique_ptr<FileDescriptor> socket =
ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
constexpr int kInvalidNegative = -2;
EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS,
&kInvalidNegative, sizeof(kInvalidNegative)),
SyscallFailsWithErrno(EINVAL));
}
TEST_P(IPv6UnboundSocketTest, SetTtlDoesNotAffectHopLimit) {
std::unique_ptr<FileDescriptor> socket =
ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
int get = -1;
socklen_t get_sz = sizeof(get);
ASSERT_THAT(
getsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &get, &get_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get_sz, sizeof(get));
const int set = (get % 255) + 1;
ASSERT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_TTL, &set, sizeof(set)),
SyscallSucceedsWithValue(0));
int get2 = -1;
socklen_t get2_sz = sizeof(get2);
ASSERT_THAT(getsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &get2,
&get2_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get2_sz, sizeof(get2));
EXPECT_EQ(get2, get);
}
TEST_P(IPv6UnboundSocketTest, SetHopLimitDoesNotAffectTtl) {
std::unique_ptr<FileDescriptor> socket =
ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
int get = -1;
socklen_t get_sz = sizeof(get);
ASSERT_THAT(getsockopt(socket->get(), IPPROTO_IP, IP_TTL, &get, &get_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get_sz, sizeof(get));
const int set = (get % 255) + 1;
ASSERT_THAT(setsockopt(socket->get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &set,
sizeof(set)),
SyscallSucceedsWithValue(0));
int get2 = -1;
socklen_t get2_sz = sizeof(get2);
ASSERT_THAT(getsockopt(socket->get(), IPPROTO_IP, IP_TTL, &get2, &get2_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(get2_sz, sizeof(get2));
EXPECT_EQ(get2, get);
}
INSTANTIATE_TEST_SUITE_P(
IPv6UnboundSockets, IPv6UnboundSocketTest,
ValuesIn(VecCat<SocketKind>(
ApplyVec<SocketKind>(IPv6UDPUnboundSocket,
std::vector<int>{0, SOCK_NONBLOCK}),
ApplyVec<SocketKind>(IPv6TCPUnboundSocket,
std::vector{0, SOCK_NONBLOCK}))));
} // namespace
} // namespace testing
} // namespace gvisor