snapshot: sagernet/gvisor v0.0.0-20260727.0-sing-box-mod.1 + SPEC 048 guard

Обновление снапшота с v0.0.0-20250811.0 на пин, которого требует
sing-box после мержа 235 коммитов (upstream d620bbbf2 "Update gvisor to
20260727.0"). Прежний снапшот был взят 2026-08-04 ровно с той версии,
на которой тогда стоял апстрим; разрыв возник 2026-08-05 вместе с его
бампом.

За год апстрим-gvisor изменил ~14 000 строк в 292 файлах. Значимое для
нас — сетевой стек: tcp/connect.go (PMTU-discovery + исправление
начального RTT/RTO: раньше задержка ACK внутри стека завышала стартовый
таймаут на несколько RTT), tcp/snd.go, tcp/rcv.go, stack/conntrack.go,
stack/packet_buffer.go. Всего 30 файлов в TCP и 37 в stack.

Баг SPEC 048 апстрим НЕ исправил — проверено по коду новой версии:
handleConnecting по-прежнему проверяет состояние endpoint'а, но не ep.h,
а performHandshake так же зануляет h и отпускает мьютекс до Close().
Поэтому guard перенесён (12 строк) вместе со своим тестом (45 строк).

Red/green проверен на новой базе: без guard'а тест падает с той же
nil-паникой, что в полевом крашдампе; с ним зелёный.
This commit is contained in:
Leadaxe 2026-08-05 14:53:31 +03:00
parent ffebe42860
commit 117243aa02
293 changed files with 16413 additions and 2842 deletions

View file

@ -130,6 +130,11 @@ func (e *endpoint) MTU() uint32 {
return lmtu - uint32(e.MaxHeaderLength())
}
// EndpointHeaderSize returns the size necessary for the ARP header.
func (e *endpoint) EndpointHeaderSize() uint32 {
return header.ARPSize
}
func (e *endpoint) MaxHeaderLength() uint16 {
return e.nic.MaxHeaderLength() + header.ARPSize
}

View file

@ -302,6 +302,7 @@ type PacketFragmenter struct {
fragmentCount int
currentFragment int
fragmentOffset int
mark uint32
}
// MakePacketFragmenter prepares the struct needed for packet fragmentation.
@ -332,6 +333,7 @@ func MakePacketFragmenter(pkt *stack.PacketBuffer, fragmentPayloadLen uint32, re
reserve: reserve,
fragmentPayloadLen: int(fragmentPayloadLen),
fragmentCount: int(fragmentCount),
mark: pkt.Mark,
}
}
@ -351,6 +353,7 @@ func (pf *PacketFragmenter) BuildNextFragment() (*stack.PacketBuffer, int, int,
fragPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: pf.reserve,
Mark: pf.mark,
})
// Copy data for the fragment.

View file

@ -103,8 +103,21 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
}
holeFound = true
// IPv6: rfc8200#section-4.5
// Changed the text to require that IPv6 nodes must not create
// overlapping fragments. Also, when reassembling an IPv6
// datagram, if one or more its constituent fragments is
// determined to be an overlapping fragment, the entire datagram
// (and any constituent fragments) must be silently discarded.
// Includes a clarification that no ICMP error message should be
// sent if overlapping fragments are received.
if currentHole.filled {
// Incoming fragment is a subset of an existing fragment.
if first != currentHole.first || last != currentHole.last {
return nil, 0, false, 0, ErrFragmentOverlap
}
// Incoming fragment is a duplicate.
// Not dropping packet incase of duplicates.
continue
}

View file

@ -39,7 +39,7 @@ type dadState struct {
extendRequest extendRequest
done *bool
timer tcpip.Timer
timer tcpip.Timer `state:"nosave"`
completionHandlers []stack.DADCompletionHandler
}

View file

@ -291,13 +291,13 @@ type GenericMulticastProtocolState struct {
robustnessVariable uint8
queryInterval time.Duration
mode protocolMode
modeTimer tcpip.Timer
modeTimer tcpip.Timer `state:"nosave"`
generalQueryV2Timer tcpip.Timer
generalQueryV2Timer tcpip.Timer `state:"nosave"`
// TODO(b/341946753): Restore when netstack is savable.
generalQueryV2TimerFiresAt time.Time `state:"nosave"`
stateChangedReportV2Timer tcpip.Timer
stateChangedReportV2Timer tcpip.Timer `state:"nosave"`
stateChangedReportV2TimerSet bool
}

View file

@ -17,7 +17,6 @@ func (d *dadState) StateFields() []string {
"nonce",
"extendRequest",
"done",
"timer",
"completionHandlers",
}
}
@ -30,8 +29,7 @@ func (d *dadState) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(0, &d.nonce)
stateSinkObject.Save(1, &d.extendRequest)
stateSinkObject.Save(2, &d.done)
stateSinkObject.Save(3, &d.timer)
stateSinkObject.Save(4, &d.completionHandlers)
stateSinkObject.Save(3, &d.completionHandlers)
}
func (d *dadState) afterLoad(context.Context) {}
@ -41,8 +39,7 @@ func (d *dadState) StateLoad(ctx context.Context, stateSourceObject state.Source
stateSourceObject.Load(0, &d.nonce)
stateSourceObject.Load(1, &d.extendRequest)
stateSourceObject.Load(2, &d.done)
stateSourceObject.Load(3, &d.timer)
stateSourceObject.Load(4, &d.completionHandlers)
stateSourceObject.Load(3, &d.completionHandlers)
}
func (d *DADOptions) StateTypeName() string {
@ -237,9 +234,6 @@ func (g *GenericMulticastProtocolState) StateFields() []string {
"robustnessVariable",
"queryInterval",
"mode",
"modeTimer",
"generalQueryV2Timer",
"stateChangedReportV2Timer",
"stateChangedReportV2TimerSet",
}
}
@ -254,10 +248,7 @@ func (g *GenericMulticastProtocolState) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(2, &g.robustnessVariable)
stateSinkObject.Save(3, &g.queryInterval)
stateSinkObject.Save(4, &g.mode)
stateSinkObject.Save(5, &g.modeTimer)
stateSinkObject.Save(6, &g.generalQueryV2Timer)
stateSinkObject.Save(7, &g.stateChangedReportV2Timer)
stateSinkObject.Save(8, &g.stateChangedReportV2TimerSet)
stateSinkObject.Save(5, &g.stateChangedReportV2TimerSet)
}
func (g *GenericMulticastProtocolState) afterLoad(context.Context) {}
@ -269,10 +260,7 @@ func (g *GenericMulticastProtocolState) StateLoad(ctx context.Context, stateSour
stateSourceObject.Load(2, &g.robustnessVariable)
stateSourceObject.Load(3, &g.queryInterval)
stateSourceObject.Load(4, &g.mode)
stateSourceObject.Load(5, &g.modeTimer)
stateSourceObject.Load(6, &g.generalQueryV2Timer)
stateSourceObject.Load(7, &g.stateChangedReportV2Timer)
stateSourceObject.Load(8, &g.stateChangedReportV2TimerSet)
stateSourceObject.Load(5, &g.stateChangedReportV2TimerSet)
}
func (m *MultiCounterIPForwardingStats) StateTypeName() string {

View file

@ -0,0 +1,293 @@
// Copyright 2026 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.
package ip
import (
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
)
// ipv6FragmentOffset returns the fragment offset of the IPv6 packet
// if present.
func ipv6FragmentOffset(pkt *stack.PacketBuffer, ipHdr header.IPv6) (uint16, bool) {
if !header.IsExtensionHeader(ipHdr.NextHeader()) {
return 0, false
}
netHeaderSlice := pkt.NetworkHeader().Slice()
if len(netHeaderSlice) <= header.IPv6MinimumSize {
return 0, false
}
// Make an iterator to walk the extension headers.
buf := buffer.MakeWithData(netHeaderSlice[header.IPv6MinimumSize:])
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(ipHdr.NextHeader()), buf)
defer it.Release()
for {
extHdr, done, err := it.Next()
if err != nil || done {
break
}
switch extHdr := extHdr.(type) {
case header.IPv6FragmentExtHdr:
offset := extHdr.FragmentOffset()
extHdr.Release()
return offset, true
default:
extHdr.Release()
}
}
return 0, false
}
// buildResetPayloadV4 builds an IPv4 + TCP Reset packet in a buffer.
func buildResetPayloadV4(ttl uint8, src, dst tcpip.Address, tcpHdr header.TCP, seq, ack uint32, flags header.TCPFlags) *buffer.View {
totalHdrLen := header.IPv4MinimumSize + header.TCPMinimumSize
v := buffer.NewViewSize(totalHdrLen)
buf := v.AsSlice()
rstIPHdr := header.IPv4(buf[:header.IPv4MinimumSize])
rstIPHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(totalHdrLen),
TTL: ttl,
Protocol: uint8(header.TCPProtocolNumber),
TOS: stack.DefaultTOS,
Flags: header.IPv4FlagDontFragment,
// Flip source and destination addresses.
SrcAddr: dst,
DstAddr: src,
})
rstTCPHdr := header.TCP(buf[header.IPv4MinimumSize:])
rstTCPHdr.Encode(&header.TCPFields{
SrcPort: tcpHdr.DestinationPort(),
DstPort: tcpHdr.SourcePort(),
SeqNum: seq,
AckNum: ack,
DataOffset: header.TCPMinimumSize,
Flags: flags,
})
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, dst, src, header.TCPMinimumSize)
rstTCPHdr.SetChecksum(0)
rstTCPHdr.SetChecksum(^rstTCPHdr.CalculateChecksum(xsum))
return v
}
// buildResetPayloadV6 builds an IPv6 + TCP Reset packet in a buffer.
func buildResetPayloadV6(hopLimit uint8, src, dst tcpip.Address, tcpHdr header.TCP, seq, ack uint32, flags header.TCPFlags) *buffer.View {
totalHdrLen := header.IPv6MinimumSize + header.TCPMinimumSize
v := buffer.NewViewSize(totalHdrLen)
buf := v.AsSlice()
rstIPHdr := header.IPv6(buf[:header.IPv6MinimumSize])
rstIPHdr.Encode(&header.IPv6Fields{
PayloadLength: uint16(header.TCPMinimumSize),
TransportProtocol: header.TCPProtocolNumber,
HopLimit: hopLimit,
// Flip source and destination addresses.
SrcAddr: dst,
DstAddr: src,
})
rstTCPHdr := header.TCP(buf[header.IPv6MinimumSize:])
rstTCPHdr.Encode(&header.TCPFields{
SrcPort: tcpHdr.DestinationPort(),
DstPort: tcpHdr.SourcePort(),
SeqNum: seq,
AckNum: ack,
DataOffset: header.TCPMinimumSize,
Flags: flags,
})
// Compute TCP checksum.
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, dst, src, header.TCPMinimumSize)
rstTCPHdr.SetChecksum(0)
rstTCPHdr.SetChecksum(^rstTCPHdr.CalculateChecksum(xsum))
return v
}
// RejectWithTCPReset sends a TCP reset in response to the packet.
//
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_send_reset[6]()
func RejectWithTCPReset(pkt *stack.PacketBuffer, netProto tcpip.NetworkProtocolNumber, stk *stack.Stack, deliveredLocally bool) tcpip.Error {
var src, dst tcpip.Address
var ttl uint8
isFragment := false
switch netProto {
case header.IPv4ProtocolNumber:
// Ref: net/ipv4/netfilter/nf_reject_ipv4.c:nf_reject_ip_tcphdr_get
ipHdr := header.IPv4(pkt.NetworkHeader().Slice())
if len(ipHdr) < header.IPv4MinimumSize {
return nil
}
if ipHdr.Protocol() != uint8(header.TCPProtocolNumber) {
return nil
}
if ipHdr.FragmentOffset() != 0 {
return nil
}
isFragment = ipHdr.More()
src = ipHdr.SourceAddress()
dst = ipHdr.DestinationAddress()
// Ref: net/ipv4/netfilter/nf_reject_ipv4.c:nf_send_reset
if header.IsV4MulticastAddress(dst) ||
header.IsV4MulticastAddress(src) ||
pkt.NetworkPacketInfo.LocalAddressBroadcast ||
pkt.PktType == tcpip.PacketBroadcast || pkt.PktType == tcpip.PacketMulticast ||
src == header.IPv4Any || dst == header.IPv4Any {
return nil
}
case header.IPv6ProtocolNumber:
// Ref: net/ipv6/netfilter/nf_reject_ipv6.c:nf_reject_ip6_tcphdr_get
ipHdr := header.IPv6(pkt.NetworkHeader().Slice())
if len(ipHdr) < header.IPv6MinimumSize {
return nil
}
fragOffset, ok := ipv6FragmentOffset(pkt, ipHdr)
if ok && fragOffset != 0 {
return nil
}
isFragment = ok
src = ipHdr.SourceAddress()
dst = ipHdr.DestinationAddress()
// Ref: net/ipv6/netfilter/nf_reject_ipv6.c:nf_send_reset6
if header.IsV6MulticastAddress(src) || header.IsV6MulticastAddress(dst) ||
header.IsV4MappedAddress(src) || header.IsV4MappedAddress(dst) ||
src == header.IPv6Any || dst == header.IPv6Any ||
pkt.PktType == tcpip.PacketBroadcast || pkt.PktType == tcpip.PacketMulticast {
return nil
}
default:
return nil
}
tcpHdr := func(pkt *stack.PacketBuffer) header.TCP {
// If 0 < len(transportHdr) < header.TCPMinimumSize, then the TCP header is invalid.
// Assuming a TCP packet,
// if the TCP header was parsed, the
// len should be >= header.TCPMinimumSize;
// else the TCP header was not parsed and the len should be 0.
transportHdr := pkt.TransportHeader().Slice()
if len(transportHdr) >= header.TCPMinimumSize {
return header.TCP(transportHdr)
}
if len(transportHdr) != 0 {
return nil
}
// In the case of fragmented TCP packets, the TCP header may not be parsed.
// Pull up the TCP header from the payload.
b, ok := pkt.Data().PullUp(header.TCPMinimumSize)
if !ok {
return nil
}
hdr := header.TCP(b)
hdrLen := int(hdr.DataOffset())
if hdrLen < header.TCPMinimumSize || pkt.Data().Size() < hdrLen {
return nil
}
tcpHdr, ok := pkt.Data().Consume(hdrLen)
if !ok {
return nil
}
pkt.TransportProtocolNumber = header.TCPProtocolNumber
return header.TCP(tcpHdr)
}(pkt)
if tcpHdr == nil {
return nil
}
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_reject_ip[6]_tcphdr_get()
// No RST for RST as this will cause a loop.
if tcpHdr.Flags().Contains(header.TCPFlagRst) {
return nil
}
// Check checksum integrity only for non-fragmented packets.
// We don't support refragmentation(nf_defrag) before REJECT,
// so we can't validate the checksum for fragmented packets.
if !isFragment {
// Check checksum integrity.
if !pkt.RXChecksumValidated && !tcpHdr.IsChecksumValid(src, dst, pkt.Data().Checksum(), uint16(pkt.Data().Size())) {
return nil
}
}
localAddr := dst
if !deliveredLocally {
// If the packet wasn't delivered locally, do not use the packet's destination
// address as the response's source address as we should not own the
// destination address.
localAddr = tcpip.Address{}
}
route, err := stk.FindRoute(0 /*nicID*/, localAddr, src, netProto, false /* multicastLoop */)
if err != nil {
return err
}
defer route.Release()
ttl = route.DefaultTTL()
var seq uint32
var ack uint32
payloadLen := uint32(pkt.Data().Size())
flags := header.TCPFlagRst
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_reject_ip[6]_tcphdr_put()
if tcpHdr.Flags()&header.TCPFlagAck != 0 {
seq = tcpHdr.AckNumber()
} else {
flags |= header.TCPFlagAck
ack = tcpHdr.SequenceNumber() + payloadLen
if tcpHdr.Flags()&header.TCPFlagSyn != 0 {
ack++
}
if tcpHdr.Flags()&header.TCPFlagFin != 0 {
ack++
}
}
var v *buffer.View
if netProto == header.IPv4ProtocolNumber {
v = buildResetPayloadV4(ttl, src, dst, tcpHdr, seq, ack, flags)
} else {
v = buildResetPayloadV6(ttl, src, dst, tcpHdr, seq, ack, flags)
}
rstPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(route.MaxHeaderLength()),
Payload: buffer.MakeWithView(v),
})
rstPkt.TransportProtocolNumber = header.TCPProtocolNumber
defer rstPkt.DecRef()
// TODO: b/521536712 - Add support for mark propagation.
if err := route.WriteHeaderIncludedPacket(rstPkt); err != nil {
return err
}
return nil
}

View file

@ -16,7 +16,6 @@ func (r *RouteTable) StateFields() []string {
return []string{
"installedRoutes",
"pendingRoutes",
"cleanupPendingRoutesTimer",
"isCleanupRoutineRunning",
"config",
}
@ -29,9 +28,8 @@ func (r *RouteTable) StateSave(stateSinkObject state.Sink) {
r.beforeSave()
stateSinkObject.Save(0, &r.installedRoutes)
stateSinkObject.Save(1, &r.pendingRoutes)
stateSinkObject.Save(2, &r.cleanupPendingRoutesTimer)
stateSinkObject.Save(3, &r.isCleanupRoutineRunning)
stateSinkObject.Save(4, &r.config)
stateSinkObject.Save(2, &r.isCleanupRoutineRunning)
stateSinkObject.Save(3, &r.config)
}
func (r *RouteTable) afterLoad(context.Context) {}
@ -40,9 +38,8 @@ func (r *RouteTable) afterLoad(context.Context) {}
func (r *RouteTable) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &r.installedRoutes)
stateSourceObject.Load(1, &r.pendingRoutes)
stateSourceObject.Load(2, &r.cleanupPendingRoutesTimer)
stateSourceObject.Load(3, &r.isCleanupRoutineRunning)
stateSourceObject.Load(4, &r.config)
stateSourceObject.Load(2, &r.isCleanupRoutineRunning)
stateSourceObject.Load(3, &r.config)
}
func (r *InstalledRoute) StateTypeName() string {

View file

@ -57,7 +57,7 @@ type RouteTable struct {
// cleanupPendingRoutesTimer is a timer that triggers a routine to remove
// pending routes that are expired.
// +checklocks:pendingMu
cleanupPendingRoutesTimer tcpip.Timer
cleanupPendingRoutesTimer tcpip.Timer `state:"nosave"`
// +checklocks:pendingMu
isCleanupRoutineRunning bool

View file

@ -16,11 +16,13 @@ package ipv4
import (
"fmt"
"math"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/header/parse"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
)
@ -343,10 +345,47 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
switch h.Type() {
case header.ICMPv4Echo:
received.echoRequest.Increment()
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
// DeliverTransportPacket may modify pkt so don't use it beyond
// this point. Make a deep copy of the data before pkt gets sent as we will
// be modifying fields. Both the ICMP header (with its type modified to
// EchoReply) and payload are reused in the reply packet.
//
// TODO(gvisor.dev/issue/4399): The copy may not be needed if there are no
// waiting endpoints. Consider moving responsibility for doing the copy to
// DeliverTransportPacket so that is is only done when needed.
replyData := stack.PayloadSince(pkt.TransportHeader())
defer replyData.Release()
localAddressTemporary := pkt.NetworkPacketInfo.LocalAddressTemporary
localAddressBroadcast := pkt.NetworkPacketInfo.LocalAddressBroadcast
// It's possible that a raw socket or per-stack default handler expects
// to receive this packet.
defaultHandlerHandled := false
if dispatcher, ok := e.dispatcher.(stack.TransportDispatcherWithDefaultHandlerResult); ok {
_, defaultHandlerHandled = dispatcher.DeliverTransportPacketWithDefaultHandlerResult(header.ICMPv4ProtocolNumber, pkt)
} else {
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
}
pkt = nil
// Skip the built-in ICMP echo reply if the request was consumed by a
// per-stack default handler. Also preserve the IPv4 behavior for
// temporary local addresses: the packet is delivered above, but the
// stack does not synthesize an echo reply for it.
if defaultHandlerHandled || localAddressTemporary {
return
}
e.sendICMPEchoReply(replyData, iph, newOptions, localAddressBroadcast)
case header.ICMPv4EchoReply:
received.echoReply.Increment()
// ICMP sockets expect the ICMP header to be present, so we don't consume
// the ICMP header.
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
case header.ICMPv4DstUnreachable:
received.dstUnreachable.Increment()
@ -411,6 +450,98 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
}
}
func (e *endpoint) sendICMPEchoReply(replyData *buffer.View, ipHdr header.IPv4, newOptions header.IPv4Options, localAddressBroadcast bool) {
sent := e.stats.icmp.packetsSent
if !e.protocol.allowICMPReply(header.ICMPv4EchoReply, header.ICMPv4UnusedCode) {
sent.rateLimited.Increment()
return
}
// As per RFC 1122 section 3.2.1.3, when a host sends any datagram, the IP
// source address MUST be one of its own IP addresses (but not a broadcast
// or multicast address).
localAddr := ipHdr.DestinationAddress()
if localAddressBroadcast || header.IsV4MulticastAddress(localAddr) {
localAddr = tcpip.Address{}
}
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, ipHdr.SourceAddress(), ProtocolNumber, false /* multicastLoop */)
if err != nil {
// If we cannot find a route to the destination, silently drop the packet.
return
}
defer r.Release()
outgoingEP, ok := e.protocol.getEndpointForNIC(r.NICID())
if !ok {
// The outgoing NIC went away.
sent.dropped.Increment()
return
}
// Because IP and ICMP are so closely intertwined, we need to handcraft our
// IP header to be able to follow RFC 792. The wording on page 13 is as
// follows:
// IP Fields:
// Addresses
// The address of the source in an echo message will be the
// destination of the echo reply message. To form an echo reply
// message, the source and destination addresses are simply reversed,
// the type code changed to 0, and the checksum recomputed.
//
// This was interpreted by early implementors to mean that all options must
// be copied from the echo request IP header to the echo reply IP header
// and this behaviour is still relied upon by some applications.
//
// Create a copy of the IP header we received, options and all, and change
// The fields we need to alter.
//
// We need to produce the entire packet in the data segment in order to
// use WriteHeaderIncludedPacket(). WriteHeaderIncludedPacket sets the
// total length and the header checksum so we don't need to set those here.
//
// Take the base of the incoming request IP header but replace the options.
replyHeaderLength := uint8(header.IPv4MinimumSize + len(newOptions))
replyIPHdrView := buffer.NewView(int(replyHeaderLength))
replyIPHdrView.Write(ipHdr[:header.IPv4MinimumSize])
replyIPHdrView.Write(newOptions)
replyIPHdr := header.IPv4(replyIPHdrView.AsSlice())
replyIPHdr.SetHeaderLength(replyHeaderLength)
replyIPHdr.SetSourceAddress(r.LocalAddress())
replyIPHdr.SetDestinationAddress(r.RemoteAddress())
replyIPHdr.SetTTL(r.DefaultTTL())
replyIPHdr.SetTotalLength(uint16(len(replyIPHdr) + len(replyData.AsSlice())))
replyIPHdr.SetChecksum(0)
replyIPHdr.SetChecksum(^replyIPHdr.CalculateChecksum())
replyICMPHdr := header.ICMPv4(replyData.AsSlice())
replyICMPHdr.SetType(header.ICMPv4EchoReply)
replyICMPHdr.SetChecksum(0)
replyICMPHdr.SetChecksum(^checksum.Checksum(replyData.AsSlice(), 0))
replyBuf := buffer.MakeWithView(replyIPHdrView)
replyBuf.Append(replyData.Clone())
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()),
Payload: replyBuf,
})
defer replyPkt.DecRef()
// Populate the network/transport headers in the packet buffer so the
// ICMP packet goes through IPTables.
if ok := parse.IPv4(replyPkt); !ok {
panic("expected to parse IPv4 header we just created")
}
if ok := parse.ICMPv4(replyPkt); !ok {
panic("expected to parse ICMPv4 header we just created")
}
if err := outgoingEP.writePacket(r, replyPkt); err != nil {
sent.dropped.Increment()
return
}
sent.echoReply.Increment()
}
// ======= ICMP Error packet generation =========
// icmpReason is a marker interface for IPv4 specific ICMP errors.
@ -479,7 +610,12 @@ func (*icmpReasonNetworkUnreachable) isICMPReason() {}
// icmpReasonFragmentationNeeded is an error where a packet requires
// fragmentation while also having the Don't Fragment flag set, as per RFC 792
// page 3, Destination Unreachable Message.
type icmpReasonFragmentationNeeded struct{}
type icmpReasonFragmentationNeeded struct {
// mtu is the MTU of the next-hop link. Per RFC 1191 §4, this value
// must be included in the ICMP Fragmentation Needed message so the
// sender can update its path MTU cache.
mtu uint32
}
func (*icmpReasonFragmentationNeeded) isICMPReason() {}
@ -584,30 +720,36 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer, deliv
}
sent := netEP.stats.icmp.packetsSent
icmpType, icmpCode, counter, pointer := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte) {
icmpType, icmpCode, counter, pointer, nextHopMTU := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte, uint16) {
switch reason := reason.(type) {
case *icmpReasonNetworkProhibited:
return header.ICMPv4DstUnreachable, header.ICMPv4NetProhibited, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4NetProhibited, sent.dstUnreachable, 0, 0
case *icmpReasonHostProhibited:
return header.ICMPv4DstUnreachable, header.ICMPv4HostProhibited, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4HostProhibited, sent.dstUnreachable, 0, 0
case *icmpReasonAdministrativelyProhibited:
return header.ICMPv4DstUnreachable, header.ICMPv4AdminProhibited, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4AdminProhibited, sent.dstUnreachable, 0, 0
case *icmpReasonPortUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0, 0
case *icmpReasonProtoUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0, 0
case *icmpReasonNetworkUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0, 0
case *icmpReasonHostUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0, 0
case *icmpReasonFragmentationNeeded:
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0
// Per RFC 1191 §4, include the next-hop MTU in the ICMP message.
// Cap at MaxUint16 since the field is 16 bits wide.
mtu := reason.mtu
if mtu > math.MaxUint16 {
mtu = math.MaxUint16
}
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0, uint16(mtu)
case *icmpReasonTTLExceeded:
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0, 0
case *icmpReasonReassemblyTimeout:
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0, 0
case *icmpReasonParamProblem:
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer, 0
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
@ -676,6 +818,7 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer, deliv
icmpHdr.SetCode(icmpCode)
icmpHdr.SetType(icmpType)
icmpHdr.SetPointer(pointer)
icmpHdr.SetMTU(nextHopMTU)
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, icmpPkt.Data().Checksum()))
if err := route.WritePacket(

View file

@ -26,6 +26,7 @@ import (
"github.com/sagernet/gvisor/pkg/log"
"github.com/sagernet/gvisor/pkg/sync"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/header/parse"
"github.com/sagernet/gvisor/pkg/tcpip/network/hash"
@ -84,6 +85,7 @@ var (
_ IGMPEndpoint = (*endpoint)(nil)
)
// +checklocksalias:igmp.ep.mu=mu
// +stateify savable
type endpoint struct {
nic stack.NetworkInterface
@ -128,13 +130,11 @@ func (e *endpoint) GetIGMPVersion() IGMPVersion {
}
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) setIGMPVersionLocked(v IGMPVersion) IGMPVersion {
return e.igmp.setVersion(v)
}
// +checklocksread:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) getIGMPVersionLocked() IGMPVersion {
return e.igmp.getVersion()
}
@ -293,7 +293,6 @@ func (e *endpoint) Enable() tcpip.Error {
}
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) enableLocked() tcpip.Error {
// If the NIC is not enabled, the endpoint can't do anything meaningful so
// don't enable the endpoint.
@ -364,7 +363,6 @@ func (e *endpoint) Disable() {
}
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) disableLocked() {
if !e.isEnabled() {
return
@ -428,6 +426,11 @@ func (e *endpoint) MTU() uint32 {
return networkMTU
}
// EndpointHeaderSize returns the size necessary for the IPv4 header.
func (e *endpoint) EndpointHeaderSize() uint32 {
return header.IPv4MinimumSize
}
// MaxHeaderLength returns the maximum length needed by ipv4 headers (and
// underlying protocols).
func (e *endpoint) MaxHeaderLength() uint16 {
@ -519,6 +522,48 @@ func (e *endpoint) handleFragments(_ *stack.Route, networkMTU uint32, pkt *stack
}
}
// recalculateChecksum recalculates the checksum of a TCP packet.
func recalculateChecksum(pkt *stack.PacketBuffer, r *stack.Route) tcpip.Error {
// RXChecksumValidated indicates that checksum verification may be
// safely skipped.
if pkt.RXChecksumValidated {
return nil
}
// NeedsCsum is set if the checksum offload is enabled, so no need to
// calculate the checksum.
if pkt.GSOOptions.Type != stack.GSONone && pkt.GSOOptions.NeedsCsum {
return nil
}
transportHeader := pkt.TransportHeader().Slice()
netHdr := header.IPv4(pkt.NetworkHeader().Slice())
switch pkt.TransportProtocolNumber {
case header.TCPProtocolNumber:
if len(transportHeader) < header.TCPMinimumSize {
return &tcpip.ErrMalformedHeader{}
}
tcp := header.TCP(transportHeader)
xsum := r.PseudoHeaderChecksum(header.TCPProtocolNumber, netHdr.PayloadLength())
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
tcp.SetChecksum(0)
tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
case header.UDPProtocolNumber:
if len(transportHeader) < header.UDPMinimumSize {
return &tcpip.ErrMalformedHeader{}
}
udp := header.UDP(transportHeader)
xsum := r.PseudoHeaderChecksum(header.UDPProtocolNumber, netHdr.PayloadLength())
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
udp.SetChecksum(0)
csum := ^udp.CalculateChecksum(xsum)
// RFC 768: If the computed checksum is zero, it is transmitted as all ones.
if csum == 0 {
csum = 0xFFFF
}
udp.SetChecksum(csum)
}
return nil
}
// WritePacket writes a packet to the given destination address and protocol.
func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams, pkt *stack.PacketBuffer) tcpip.Error {
if err := e.addIPHeader(r.LocalAddress(), r.RemoteAddress(), pkt, params, nil /* options */); err != nil {
@ -531,16 +576,23 @@ func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams,
func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer) tcpip.Error {
netHeader := header.IPv4(pkt.NetworkHeader().Slice())
dstAddr := netHeader.DestinationAddress()
stk := e.protocol.stack
// iptables filtering. All packets that reach here are locally
// generated.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckOutput(pkt, r, outNicName); !ok {
// iptables filtering. All packets that reach here are locally generated.
outNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckOutput(pkt, r, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesOutputDropped.Increment()
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckOutput(pkt, r, stack.IP) {
// nftables is telling us to drop the packet.
return nil
}
}
// If the packet is manipulated as per DNAT Output rules, handle packet
// based on destination address and do not send the packet to link
// layer.
@ -555,6 +607,37 @@ func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer) tcpip.Er
ep.handleLocalPacket(pkt, true /* canSkipRXChecksum */)
return nil
}
// Similar to the `ip_route_me_harder` in the kernel,
// we need to find a new route for the packet.
// Implementation is similar to the func forwardUnicastPacket.
stk := e.protocol.stack
newRoute, err := stk.FindRoute(0 /* nic id */, netHeader.SourceAddress(), newDstAddr, header.IPv4ProtocolNumber, false /* multicastLoop */)
if err != nil {
return err // Drop the packet
}
// Release the new route on exit.
defer newRoute.Release()
// Check if we need to recalculate the checksum.
// If the original route did not require a checksum but the new one does,
// we must calculate the full checksum; otherwise, NAT should have already
// done it.
if !r.RequiresTXTransportChecksum() && newRoute.RequiresTXTransportChecksum() {
if err := recalculateChecksum(pkt, newRoute); err != nil {
return err // Drop the packet
}
}
// Update the route to the new route.
r = newRoute
// Use the new endpoint to write the packet.
forwardToEp, ok := e.protocol.getEndpointForNIC(r.NICID())
if !ok {
return &tcpip.ErrUnknownNICID{}
}
return forwardToEp.writePacketPostRouting(r, pkt, true /* headerIncluded */)
}
return e.writePacketPostRouting(r, pkt, false /* headerIncluded */)
@ -571,15 +654,23 @@ func (e *endpoint) writePacketPostRouting(r *stack.Route, pkt *stack.PacketBuffe
return nil
}
stk := e.protocol.stack
// Postrouting NAT can only change the source address, and does not alter the
// route or outgoing interface of the packet.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
outNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesPostroutingDropped.Increment()
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckPostrouting(pkt, r, stack.IP) {
// nftables is telling us to drop the packet.
return nil
}
}
stats := e.stats.ip
networkMTU, err := calculateNetworkMTU(e.nic.MTU(), uint32(len(pkt.NetworkHeader().Slice())))
@ -690,6 +781,13 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckForward(pkt, route, stack.IP) {
// nftables is telling us to drop the packet.
return nil
}
}
// We need to do a deep copy of the IP packet because
// WriteHeaderIncludedPacket may modify the packet buffer, but we do
// not own it.
@ -725,6 +823,10 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
newHdr.SetChecksum(0)
newHdr.SetChecksum(^newHdr.CalculateChecksum())
if route.RequiresTXTransportChecksum() {
newPkt.CalculateTransportChecksum()
}
switch err := forwardToEp.writePacketPostRouting(route, newPkt, true /* headerIncluded */); err.(type) {
case nil:
return nil
@ -738,7 +840,9 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
// WriteHeaderIncludedPacket checks for the presence of the Don't Fragment bit
// while sending the packet and returns this error iff fragmentation is
// necessary and the bit is also set.
_ = e.protocol.returnError(&icmpReasonFragmentationNeeded{}, pkt, false /* deliveredLocally */)
_ = e.protocol.returnError(&icmpReasonFragmentationNeeded{
mtu: forwardToEp.nic.MTU(),
}, pkt, false /* deliveredLocally */)
return &ip.ErrMessageTooLong{}
case *tcpip.ErrNoBufferSpace:
return &ip.ErrOutgoingDeviceNoBufferSpace{}
@ -790,6 +894,13 @@ func (e *endpoint) forwardUnicastPacket(pkt *stack.PacketBuffer) ip.ForwardingEr
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckForward(pkt, nil /* route */, stack.IP) {
// nftables is telling us to drop the packet.
return nil
}
}
// The packet originally arrived on e so provide its NIC as the input NIC.
ep.handleValidatedPacket(h, pkt, e.nic.Name() /* inNICName */)
return nil
@ -857,7 +968,8 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
if e.protocol.stack.HandleLocal() {
stk := e.protocol.stack
if stk.HandleLocal() {
addressEndpoint := e.AcquireAssignedAddress(header.IPv4(pkt.NetworkHeader().Slice()).SourceAddress(), e.nic.Promiscuous(), stack.CanBePrimaryEndpoint, true /* readOnly */)
if addressEndpoint != nil {
// The source address is one of our own, so we never should have gotten
@ -868,13 +980,22 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
nicID := e.nic.ID()
// Loopback traffic skips the prerouting chain.
inNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
inNicName := stk.FindNICNameFromID(nicID)
pkt.InputNICID = nicID
if ok := stk.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
// iptables is telling us to drop the packet.
stats.IPTablesPreroutingDropped.Increment()
return
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckPrerouting(pkt, nil /* route */, stack.IP) {
// nftables is telling us to drop the packet.
return
}
}
}
// CheckPrerouting can modify the backing storage of the packet, so refresh
// the header.
@ -1208,14 +1329,22 @@ func (e *endpoint) handleForwardingError(err ip.ForwardingError) {
func (e *endpoint) deliverPacketLocally(h header.IPv4, pkt *stack.PacketBuffer, inNICName string) {
stats := e.stats
stk := e.protocol.stack
// iptables filtering. All packets that reach here are intended for
// this machine and will not be forwarded.
if ok := e.protocol.stack.IPTables().CheckInput(pkt, inNICName); !ok {
if ok := stk.IPTables().CheckInput(pkt, inNICName); !ok {
// iptables is telling us to drop the packet.
stats.ip.IPTablesInputDropped.Increment()
return
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckInput(pkt, nil /* route */, stack.IP) {
// nftables is telling us to drop the packet.
return
}
}
if h.More() || h.FragmentOffset() != 0 {
if pkt.Data().Size()+len(pkt.TransportHeader().Slice()) == 0 {
// Drop the packet as it's marked as a fragment but has
@ -1327,7 +1456,7 @@ func (e *endpoint) deliverPacketLocally(h header.IPv4, pkt *stack.PacketBuffer,
}
if p == header.IGMPProtocolNumber {
e.mu.Lock()
e.igmp.handleIGMP(pkt, hasRouterAlertOption) // +checklocksforce: e == e.igmp.ep.
e.igmp.handleIGMP(pkt, hasRouterAlertOption)
e.mu.Unlock()
return
}
@ -1377,7 +1506,6 @@ func (e *endpoint) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, p
// sendQueuedReports sends queued igmp reports.
//
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) sendQueuedReports() {
e.igmp.sendQueuedReports()
}
@ -1463,7 +1591,6 @@ func (e *endpoint) JoinGroup(addr tcpip.Address) tcpip.Error {
// joinGroupLocked is like JoinGroup but with locking requirements.
//
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) joinGroupLocked(addr tcpip.Address) tcpip.Error {
if !header.IsV4MulticastAddress(addr) {
return &tcpip.ErrBadAddress{}
@ -1483,7 +1610,6 @@ func (e *endpoint) LeaveGroup(addr tcpip.Address) tcpip.Error {
// leaveGroupLocked is like LeaveGroup but with locking requirements.
//
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) leaveGroupLocked(addr tcpip.Address) tcpip.Error {
return e.igmp.leaveGroup(addr)
}
@ -1492,7 +1618,7 @@ func (e *endpoint) leaveGroupLocked(addr tcpip.Address) tcpip.Error {
func (e *endpoint) IsInGroup(addr tcpip.Address) bool {
e.mu.RLock()
defer e.mu.RUnlock()
return e.igmp.isInGroup(addr) // +checklocksforce: e.mu==e.igmp.ep.mu.
return e.igmp.isInGroup(addr)
}
// Stats implements stack.NetworkEndpoint.
@ -1860,6 +1986,8 @@ func (p *protocol) SendRejectionError(pkt *stack.PacketBuffer, rejectWith stack.
return p.returnError(&icmpReasonHostProhibited{}, pkt, inputHook)
case stack.RejectIPv4WithICMPAdminProhibited:
return p.returnError(&icmpReasonAdministrativelyProhibited{}, pkt, inputHook)
case stack.RejectIPv4WithTCPReset:
return ip.RejectWithTCPReset(pkt, ProtocolNumber, p.stack, inputHook)
default:
panic(fmt.Sprintf("unhandled %[1]T = %[1]d", rejectWith))
}

View file

@ -654,11 +654,29 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
case header.ICMPv6EchoRequest:
received.echoRequest.Increment()
if len(h) < header.ICMPv6EchoMinimumSize {
received.invalid.Increment()
replyPayload := pkt.Data().ToBuffer()
replyHeader := make([]byte, header.ICMPv6EchoMinimumSize)
copy(replyHeader, h[:header.ICMPv6EchoMinimumSize])
// It's possible that a raw socket or per-stack default handler expects
// to receive this packet.
defaultHandlerHandled := false
if dispatcher, ok := e.dispatcher.(stack.TransportDispatcherWithDefaultHandlerResult); ok {
_, defaultHandlerHandled = dispatcher.DeliverTransportPacketWithDefaultHandlerResult(header.ICMPv6ProtocolNumber, pkt)
} else {
e.dispatcher.DeliverTransportPacket(header.ICMPv6ProtocolNumber, pkt)
}
pkt = nil
// Skip the built-in ICMP echo reply if the request was consumed by a
// per-stack default handler.
if defaultHandlerHandled {
replyPayload.Release()
return
}
e.dispatcher.DeliverTransportPacket(header.ICMPv6ProtocolNumber, pkt)
e.sendICMPEchoReply(replyPayload, replyHeader, srcAddr, dstAddr, iph)
case header.ICMPv6EchoReply:
received.echoReply.Increment()
if len(h) < header.ICMPv6EchoMinimumSize {
@ -666,6 +684,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
return
}
e.dispatcher.DeliverTransportPacket(header.ICMPv6ProtocolNumber, pkt)
case header.ICMPv6TimeExceeded:
received.timeExceeded.Increment()
@ -852,6 +871,62 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
}
}
func (e *endpoint) sendICMPEchoReply(replyPayload buffer.Buffer, replyHeader []byte, srcAddr, dstAddr tcpip.Address, ipHdr header.IPv6) {
sent := e.stats.icmp.packetsSent
// As per RFC 4291 section 2.7, multicast addresses must not be used as
// source addresses in IPv6 packets.
localAddr := dstAddr
if header.IsV6MulticastAddress(dstAddr) {
localAddr = tcpip.Address{}
}
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, srcAddr, ProtocolNumber, false /* multicastLoop */)
if err != nil {
// If we cannot find a route to the destination, silently drop the packet.
replyPayload.Release()
return
}
defer r.Release()
if !e.protocol.allowICMPReply(header.ICMPv6EchoReply) {
sent.rateLimited.Increment()
replyPayload.Release()
return
}
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()) + header.ICMPv6EchoMinimumSize,
Payload: replyPayload,
})
defer replyPkt.DecRef()
icmp := header.ICMPv6(replyPkt.TransportHeader().Push(header.ICMPv6EchoMinimumSize))
replyPkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
copy(icmp, replyHeader)
icmp.SetType(header.ICMPv6EchoReply)
replyData := replyPkt.Data()
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmp,
Src: r.LocalAddress(),
Dst: r.RemoteAddress(),
PayloadCsum: replyData.Checksum(),
PayloadLen: replyData.Size(),
}))
replyTClass, _ := ipHdr.TOS()
if err := r.WritePacket(stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
TTL: r.DefaultTTL(),
// Even though RFC 4443 does not mention anything about it, Linux uses the
// TrafficClass of the received echo request when replying.
// https://github.com/torvalds/linux/blob/0280e3c58f9/net/ipv6/icmp.c#L797
TOS: replyTClass,
}, replyPkt); err != nil {
sent.dropped.Increment()
return
}
sent.echoReply.Increment()
}
// LinkAddressProtocol implements stack.LinkAddressResolver.
func (*endpoint) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
return header.IPv6ProtocolNumber

View file

@ -674,7 +674,7 @@ func (e *endpoint) Disable() {
}
func (e *endpoint) disableLocked() {
if !e.Enabled() {
if !e.isEnabled() {
return
}
@ -730,6 +730,11 @@ func (e *endpoint) MTU() uint32 {
return networkMTU
}
// EndpointHeaderSize returns the size necessary for the IPv6 header.
func (e *endpoint) EndpointHeaderSize() uint32 {
return header.IPv6MinimumSize
}
// MaxHeaderLength returns the maximum length needed by ipv6 headers (and
// underlying protocols).
func (e *endpoint) MaxHeaderLength() uint16 {
@ -819,15 +824,23 @@ func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams,
return err
}
// iptables filtering. All packets that reach here are locally
// generated.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckOutput(pkt, r, outNicName); !ok {
stk := e.protocol.stack
// iptables filtering. All packets that reach here are locally generated.
outNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckOutput(pkt, r, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesOutputDropped.Increment()
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
// TODO: b/486197011 - Add support for NAT re-routing in IPv6.
if !nft.CheckOutput(pkt, r, stack.IP6) {
// nftables is telling us to drop the packet.
return nil
}
}
// If the packet is manipulated as per DNAT Output rules, handle packet
// based on destination address and do not send the packet to link
// layer.
@ -858,15 +871,23 @@ func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer, protocol
return nil
}
stk := e.protocol.stack
// Postrouting NAT can only change the source address, and does not alter the
// route or outgoing interface of the packet.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
outNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesPostroutingDropped.Increment()
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckPostrouting(pkt, r, stack.IP6) {
// nftables is telling us to drop the packet.
return nil
}
}
stats := e.stats.ip
networkMTU, err := calculateNetworkMTU(e.nic.MTU(), uint32(len(pkt.NetworkHeader().Slice())))
if err != nil {
@ -1007,6 +1028,13 @@ func (e *endpoint) forwardUnicastPacket(pkt *stack.PacketBuffer) ip.ForwardingEr
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckForward(pkt, nil, stack.IP6) {
// nftables is telling us to drop the packet.
return nil
}
}
// The packet originally arrived on e so provide its NIC as the input NIC.
ep.handleValidatedPacket(h, pkt, e.nic.Name() /* inNICName */)
return nil
@ -1048,6 +1076,13 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckForward(pkt, route, stack.IP6) {
// nftables is telling us to drop the packet.
return nil
}
}
hopLimit := h.HopLimit()
// We need to do a deep copy of the IP packet because
@ -1063,6 +1098,10 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
// each node that forwards the packet.
newHdr.SetHopLimit(hopLimit - 1)
if route.RequiresTXTransportChecksum() {
newPkt.CalculateTransportChecksum()
}
forwardToEp, ok := e.protocol.getEndpointForNIC(route.NICID())
if !ok {
// The interface was removed after we obtained the route.
@ -1123,7 +1162,8 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
if e.protocol.stack.HandleLocal() {
stk := e.protocol.stack
if stk.HandleLocal() {
addressEndpoint := e.AcquireAssignedAddress(header.IPv6(pkt.NetworkHeader().Slice()).SourceAddress(), e.nic.Promiscuous(), stack.CanBePrimaryEndpoint, true /* readOnly */)
if addressEndpoint != nil {
// The source address is one of our own, so we never should have gotten
@ -1135,12 +1175,19 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
// Loopback traffic skips the prerouting chain.
inNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
inNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
// iptables is telling us to drop the packet.
stats.IPTablesPreroutingDropped.Increment()
return
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckPrerouting(pkt, nil, stack.IP6) {
// nftables is telling us to drop the packet.
return
}
}
}
// CheckPrerouting can modify the backing storage of the packet, so refresh
@ -1386,15 +1433,20 @@ func (e *endpoint) handleValidatedPacket(h header.IPv6, pkt *stack.PacketBuffer,
func (e *endpoint) deliverPacketLocally(h header.IPv6, pkt *stack.PacketBuffer, inNICName string) {
stats := e.stats.ip
// iptables filtering. All packets that reach here are intended for
// this machine and need not be forwarded.
if ok := e.protocol.stack.IPTables().CheckInput(pkt, inNICName); !ok {
stk := e.protocol.stack
if ok := stk.IPTables().CheckInput(pkt, inNICName); !ok {
// iptables is telling us to drop the packet.
stats.IPTablesInputDropped.Increment()
return
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckInput(pkt, nil, stack.IP6) {
// nftables is telling us to drop the packet.
return
}
}
// Any returned error is only useful for terminating execution early, but
// we have nothing left to do, so we can drop it.
_ = e.processExtensionHeaders(h, pkt, false /* forwarding */)
@ -2692,6 +2744,8 @@ func (p *protocol) SendRejectionError(pkt *stack.PacketBuffer, rejectWith stack.
return p.returnError(&icmpReasonPortUnreachable{}, pkt, inputHook)
case stack.RejectIPv6WithICMPAdminProhibited:
return p.returnError(&icmpReasonAdministrativelyProhibited{}, pkt, inputHook)
case stack.RejectIPv6WithTCPReset:
return ip.RejectWithTCPReset(pkt, ProtocolNumber, p.stack, inputHook)
default:
panic(fmt.Sprintf("unhandled %[1]T = %[1]d", rejectWith))
}

View file

@ -525,7 +525,6 @@ func (t *timer) StateTypeName() string {
func (t *timer) StateFields() []string {
return []string{
"done",
"timer",
}
}
@ -535,7 +534,6 @@ func (t *timer) beforeSave() {}
func (t *timer) StateSave(stateSinkObject state.Sink) {
t.beforeSave()
stateSinkObject.Save(0, &t.done)
stateSinkObject.Save(1, &t.timer)
}
func (t *timer) afterLoad(context.Context) {}
@ -543,7 +541,6 @@ func (t *timer) afterLoad(context.Context) {}
// +checklocksignore
func (t *timer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &t.done)
stateSourceObject.Load(1, &t.timer)
}
func (o *offLinkRoute) StateTypeName() string {

View file

@ -471,7 +471,7 @@ type timer struct {
// done indicates to the timer that the timer was stopped.
done *bool
timer tcpip.Timer
timer tcpip.Timer `state:"nosave"`
}
// +stateify savable