mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user