Add TCP checksum verification.

PiperOrigin-RevId: 242704699
Change-Id: I87db368ca343b3b4bf4f969b17d3aa4ce2f8bd4f
This commit is contained in:
Bhasker Hariharan
2019-04-09 11:23:47 -07:00
committed by Shentubot
parent f18a8f9581
commit eaac2806ff
16 changed files with 129 additions and 57 deletions
+1
View File
@@ -154,6 +154,7 @@ var Metrics = tcpip.Stats{
SlowStartRetransmits: mustCreateMetric("/netstack/tcp/slow_start_retransmits", "Number of segments retransmitted in slow start mode."),
FastRetransmit: mustCreateMetric("/netstack/tcp/fast_retransmit", "Number of TCP segments which were fast retransmitted."),
Timeouts: mustCreateMetric("/netstack/tcp/timeouts", "Number of times RTO expired."),
ChecksumErrors: mustCreateMetric("/netstack/tcp/checksum_errors", "Number of segments dropped due to bad checksums."),
},
UDP: tcpip.UDPStats{
PacketsReceived: mustCreateMetric("/netstack/udp/packets_received", "Number of UDP datagrams received via HandlePacket."),
+32 -36
View File
@@ -22,16 +22,17 @@ import (
"gvisor.googlesource.com/gvisor/pkg/tcpip/seqnum"
)
// These constants are the offsets of the respective fields in the TCP header.
const (
srcPort = 0
dstPort = 2
seqNum = 4
ackNum = 8
dataOffset = 12
tcpFlags = 13
winSize = 14
tcpChecksum = 16
urgentPtr = 18
TCPSrcPortOffset = 0
TCPDstPortOffset = 2
TCPSeqNumOffset = 4
TCPAckNumOffset = 8
TCPDataOffset = 12
TCPFlagsOffset = 13
TCPWinSizeOffset = 14
TCPChecksumOffset = 16
TCPUrgentPtrOffset = 18
)
const (
@@ -179,27 +180,27 @@ const (
// SourcePort returns the "source port" field of the tcp header.
func (b TCP) SourcePort() uint16 {
return binary.BigEndian.Uint16(b[srcPort:])
return binary.BigEndian.Uint16(b[TCPSrcPortOffset:])
}
// DestinationPort returns the "destination port" field of the tcp header.
func (b TCP) DestinationPort() uint16 {
return binary.BigEndian.Uint16(b[dstPort:])
return binary.BigEndian.Uint16(b[TCPDstPortOffset:])
}
// SequenceNumber returns the "sequence number" field of the tcp header.
func (b TCP) SequenceNumber() uint32 {
return binary.BigEndian.Uint32(b[seqNum:])
return binary.BigEndian.Uint32(b[TCPSeqNumOffset:])
}
// AckNumber returns the "ack number" field of the tcp header.
func (b TCP) AckNumber() uint32 {
return binary.BigEndian.Uint32(b[ackNum:])
return binary.BigEndian.Uint32(b[TCPAckNumOffset:])
}
// DataOffset returns the "data offset" field of the tcp header.
func (b TCP) DataOffset() uint8 {
return (b[dataOffset] >> 4) * 4
return (b[TCPDataOffset] >> 4) * 4
}
// Payload returns the data in the tcp packet.
@@ -209,32 +210,32 @@ func (b TCP) Payload() []byte {
// Flags returns the flags field of the tcp header.
func (b TCP) Flags() uint8 {
return b[tcpFlags]
return b[TCPFlagsOffset]
}
// WindowSize returns the "window size" field of the tcp header.
func (b TCP) WindowSize() uint16 {
return binary.BigEndian.Uint16(b[winSize:])
return binary.BigEndian.Uint16(b[TCPWinSizeOffset:])
}
// Checksum returns the "checksum" field of the tcp header.
func (b TCP) Checksum() uint16 {
return binary.BigEndian.Uint16(b[tcpChecksum:])
return binary.BigEndian.Uint16(b[TCPChecksumOffset:])
}
// SetSourcePort sets the "source port" field of the tcp header.
func (b TCP) SetSourcePort(port uint16) {
binary.BigEndian.PutUint16(b[srcPort:], port)
binary.BigEndian.PutUint16(b[TCPSrcPortOffset:], port)
}
// SetDestinationPort sets the "destination port" field of the tcp header.
func (b TCP) SetDestinationPort(port uint16) {
binary.BigEndian.PutUint16(b[dstPort:], port)
binary.BigEndian.PutUint16(b[TCPDstPortOffset:], port)
}
// SetChecksum sets the checksum field of the tcp header.
func (b TCP) SetChecksum(checksum uint16) {
binary.BigEndian.PutUint16(b[tcpChecksum:], checksum)
binary.BigEndian.PutUint16(b[TCPChecksumOffset:], checksum)
}
// CalculateChecksum calculates the checksum of the tcp segment.
@@ -258,20 +259,20 @@ func (b TCP) ParsedOptions() TCPOptions {
}
func (b TCP) encodeSubset(seq, ack uint32, flags uint8, rcvwnd uint16) {
binary.BigEndian.PutUint32(b[seqNum:], seq)
binary.BigEndian.PutUint32(b[ackNum:], ack)
b[tcpFlags] = flags
binary.BigEndian.PutUint16(b[winSize:], rcvwnd)
binary.BigEndian.PutUint32(b[TCPSeqNumOffset:], seq)
binary.BigEndian.PutUint32(b[TCPAckNumOffset:], ack)
b[TCPFlagsOffset] = flags
binary.BigEndian.PutUint16(b[TCPWinSizeOffset:], rcvwnd)
}
// Encode encodes all the fields of the tcp header.
func (b TCP) Encode(t *TCPFields) {
b.encodeSubset(t.SeqNum, t.AckNum, t.Flags, t.WindowSize)
binary.BigEndian.PutUint16(b[srcPort:], t.SrcPort)
binary.BigEndian.PutUint16(b[dstPort:], t.DstPort)
b[dataOffset] = (t.DataOffset / 4) << 4
binary.BigEndian.PutUint16(b[tcpChecksum:], t.Checksum)
binary.BigEndian.PutUint16(b[urgentPtr:], t.UrgentPointer)
binary.BigEndian.PutUint16(b[TCPSrcPortOffset:], t.SrcPort)
binary.BigEndian.PutUint16(b[TCPDstPortOffset:], t.DstPort)
b[TCPDataOffset] = (t.DataOffset / 4) << 4
binary.BigEndian.PutUint16(b[TCPChecksumOffset:], t.Checksum)
binary.BigEndian.PutUint16(b[TCPUrgentPtrOffset:], t.UrgentPointer)
}
// EncodePartial updates a subset of the fields of the tcp header. It is useful
@@ -290,18 +291,13 @@ func (b TCP) EncodePartial(partialChecksum, length uint16, seqnum, acknum uint32
b.encodeSubset(seqnum, acknum, flags, rcvwnd)
// Add the contributions of the passed-in fields to the checksum.
checksum = Checksum(b[seqNum:seqNum+8], checksum)
checksum = Checksum(b[winSize:winSize+2], checksum)
checksum = Checksum(b[TCPSeqNumOffset:TCPSeqNumOffset+8], checksum)
checksum = Checksum(b[TCPWinSizeOffset:TCPWinSizeOffset+2], checksum)
// Encode the checksum.
b.SetChecksum(^checksum)
}
// TCPChecksumOffset returns offset of the checksum field.
func TCPChecksumOffset() uint16 {
return tcpChecksum
}
// ParseSynOptions parses the options received in a SYN segment and returns the
// relevant ones. opts should point to the option part of the TCP Header.
func ParseSynOptions(opts []byte, isAck bool) TCPSynOptions {
+12 -6
View File
@@ -122,13 +122,14 @@ type Options struct {
FD int
MTU uint32
EthernetHeader bool
ChecksumOffload bool
ClosedFunc func(*tcpip.Error)
Address tcpip.LinkAddress
SaveRestore bool
DisconnectOk bool
GSOMaxSize uint32
PacketDispatchMode PacketDispatchMode
TXChecksumOffload bool
RXChecksumOffload bool
}
// New creates a new fd-based endpoint.
@@ -142,8 +143,12 @@ func New(opts *Options) tcpip.LinkEndpointID {
}
caps := stack.LinkEndpointCapabilities(0)
if opts.ChecksumOffload {
caps |= stack.CapabilityChecksumOffload
if opts.RXChecksumOffload {
caps |= stack.CapabilityRXChecksumOffload
}
if opts.TXChecksumOffload {
caps |= stack.CapabilityTXChecksumOffload
}
hdrSize := 0
@@ -527,12 +532,13 @@ func (e *InjectableEndpoint) Inject(protocol tcpip.NetworkProtocolNumber, vv buf
}
// NewInjectable creates a new fd-based InjectableEndpoint.
func NewInjectable(fd int, mtu uint32) (tcpip.LinkEndpointID, *InjectableEndpoint) {
func NewInjectable(fd int, mtu uint32, capabilities stack.LinkEndpointCapabilities) (tcpip.LinkEndpointID, *InjectableEndpoint) {
syscall.SetNonblock(fd, true)
e := &InjectableEndpoint{endpoint: endpoint{
fd: fd,
mtu: mtu,
fd: fd,
mtu: mtu,
caps: capabilities,
}}
return stack.RegisterLinkEndpoint(e), e
+1 -1
View File
@@ -56,7 +56,7 @@ func (*endpoint) MTU() uint32 {
// Capabilities implements stack.LinkEndpoint.Capabilities. Loopback advertises
// itself as supporting checksum offload, but in reality it's just omitted.
func (*endpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityChecksumOffload | stack.CapabilitySaveRestore | stack.CapabilityLoopback
return stack.CapabilityRXChecksumOffload | stack.CapabilityTXChecksumOffload | stack.CapabilitySaveRestore | stack.CapabilityLoopback
}
// MaxHeaderLength implements stack.LinkEndpoint.MaxHeaderLength. Given that the
+1 -1
View File
@@ -105,7 +105,7 @@ func (m *InjectableEndpoint) WriteRawPacket(dest tcpip.Address, packet []byte) *
}
// NewInjectableEndpoint creates a new multi-endpoint injectable endpoint.
func NewInjectableEndpoint(routes map[tcpip.Address]stack.InjectableLinkEndpoint, mtu uint32) (tcpip.LinkEndpointID, *InjectableEndpoint) {
func NewInjectableEndpoint(routes map[tcpip.Address]stack.InjectableLinkEndpoint) (tcpip.LinkEndpointID, *InjectableEndpoint) {
e := &InjectableEndpoint{
routes: routes,
}
+2 -2
View File
@@ -87,8 +87,8 @@ func makeTestInjectableEndpoint(t *testing.T) (*InjectableEndpoint, *os.File, tc
if err != nil {
t.Fatal("Failed to create socket pair:", err)
}
_, underlyingEndpoint := fdbased.NewInjectable(pair[1], 6500)
_, underlyingEndpoint := fdbased.NewInjectable(pair[1], 6500, stack.CapabilityNone)
routes := map[tcpip.Address]stack.InjectableLinkEndpoint{dstIP: underlyingEndpoint}
_, endpoint := NewInjectableEndpoint(routes, 6500)
_, endpoint := NewInjectableEndpoint(routes)
return endpoint, os.NewFile(uintptr(pair[0]), "test route end"), dstIP
}
+9 -1
View File
@@ -232,7 +232,15 @@ type LinkEndpointCapabilities uint
// The following are the supported link endpoint capabilities.
const (
CapabilityChecksumOffload LinkEndpointCapabilities = 1 << iota
CapabilityNone LinkEndpointCapabilities = 0
// CapabilityTXChecksumOffload indicates that the link endpoint supports
// checksum computation for outgoing packets and the stack can skip
// computing checksums when sending packets.
CapabilityTXChecksumOffload LinkEndpointCapabilities = 1 << iota
// CapabilityRXChecksumOffload indicates that the link endpoint supports
// checksum verification on received packets and that it's safe for the
// stack to skip checksum verification.
CapabilityRXChecksumOffload
CapabilityResolutionRequired
CapabilitySaveRestore
CapabilityDisconnectOk
+3
View File
@@ -801,6 +801,9 @@ type TCPStats struct {
// Timeouts is the number of times the RTO expired.
Timeouts *StatCounter
// ChecksumErrors is the number of segments dropped due to bad checksums.
ChecksumErrors *StatCounter
}
// UDPStats collects UDP-specific stats.
+1 -1
View File
@@ -595,7 +595,7 @@ func sendTCP(r *stack.Route, id stack.TransportEndpointID, data buffer.Vectorise
// TCP header, then the kernel calculate a checksum of the
// header and data and get the right sum of the TCP packet.
tcp.SetChecksum(xsum)
} else if r.Capabilities()&stack.CapabilityChecksumOffload == 0 {
} else if r.Capabilities()&stack.CapabilityTXChecksumOffload == 0 {
xsum = header.ChecksumVV(data, xsum)
tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
}
+8 -1
View File
@@ -1447,6 +1447,13 @@ func (e *endpoint) HandlePacket(r *stack.Route, id stack.TransportEndpointID, vv
return
}
if !s.csumValid {
e.stack.Stats().MalformedRcvdPackets.Increment()
e.stack.Stats().TCP.ChecksumErrors.Increment()
s.decRef()
return
}
e.stack.Stats().TCP.ValidSegmentsReceived.Increment()
if (s.flags & header.TCPFlagRst) != 0 {
e.stack.Stats().TCP.ResetsReceived.Increment()
@@ -1721,7 +1728,7 @@ func (e *endpoint) initGSO() {
panic(fmt.Sprintf("Unknown netProto: %v", e.netProto))
}
gso.NeedsCsum = true
gso.CsumOffset = header.TCPChecksumOffset()
gso.CsumOffset = header.TCPChecksumOffset
gso.MaxSize = e.route.GSOMaxSize()
e.gso = gso
}
+1 -1
View File
@@ -68,7 +68,7 @@ func (f *Forwarder) HandlePacket(r *stack.Route, id stack.TransportEndpointID, n
defer s.decRef()
// We only care about well-formed SYN packets.
if !s.parse() || s.flags != header.TCPFlagSyn {
if !s.parse() || !s.csumValid || s.flags != header.TCPFlagSyn {
return false
}
+1 -1
View File
@@ -130,7 +130,7 @@ func (*protocol) HandleUnknownDestinationPacket(r *stack.Route, id stack.Transpo
s := newSegment(r, id, vv)
defer s.decRef()
if !s.parse() {
if !s.parse() || !s.csumValid {
return false
}
+28 -3
View File
@@ -45,6 +45,10 @@ type segment struct {
ackNumber seqnum.Value
flags uint8
window seqnum.Size
// csum is only populated for received segments.
csum uint16
// csumValid is true if the csum in the received segment is valid.
csumValid bool
// parsedOptions stores the parsed values from the options in the segment.
parsedOptions header.TCPOptions
@@ -124,7 +128,13 @@ func (s *segment) logicalLen() seqnum.Size {
// parse populates the sequence & ack numbers, flags, and window fields of the
// segment from the TCP header stored in the data. It then updates the view to
// skip the data. Returns boolean indicating if the parsing was successful.
// skip the header.
//
// Returns boolean indicating if the parsing was successful.
//
// If checksum verification is not offloaded then parse also verifies the
// TCP checksum and stores the checksum and result of checksum verification in
// the csum and csumValid fields of the segment.
func (s *segment) parse() bool {
h := header.TCP(s.data.First())
@@ -145,12 +155,27 @@ func (s *segment) parse() bool {
s.options = []byte(h[header.TCPMinimumSize:offset])
s.parsedOptions = header.ParseTCPOptions(s.options)
s.data.TrimFront(offset)
// Query the link capabilities to decide if checksum validation is
// required.
verifyChecksum := true
if s.route.Capabilities()&stack.CapabilityRXChecksumOffload != 0 {
s.csumValid = true
verifyChecksum = false
s.data.TrimFront(offset)
}
if verifyChecksum {
s.csum = h.Checksum()
xsum := s.route.PseudoHeaderChecksum(ProtocolNumber, uint16(s.data.Size()))
xsum = h.CalculateChecksum(xsum)
s.data.TrimFront(offset)
xsum = header.ChecksumVV(s.data, xsum)
s.csumValid = xsum == 0xffff
}
s.sequenceNumber = seqnum.Value(h.SequenceNumber())
s.ackNumber = seqnum.Value(h.AckNumber())
s.flags = h.Flags()
s.window = seqnum.Size(h.WindowSize())
return true
}
+27 -2
View File
@@ -2963,8 +2963,7 @@ func TestReceivedInvalidSegmentCountIncrement(t *testing.T) {
RcvWnd: 30000,
})
tcpbuf := vv.First()[header.IPv4MinimumSize:]
// 12 is the TCP header data offset.
tcpbuf[12] = ((header.TCPMinimumSize - 1) / 4) << 4
tcpbuf[header.TCPDataOffset] = ((header.TCPMinimumSize - 1) / 4) << 4
c.SendSegment(vv)
@@ -2973,6 +2972,32 @@ func TestReceivedInvalidSegmentCountIncrement(t *testing.T) {
}
}
func TestReceivedIncorrectChecksumIncrement(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
c.CreateConnected(789, 30000, nil)
stats := c.Stack().Stats()
want := stats.TCP.ChecksumErrors.Value() + 1
vv := c.BuildSegment([]byte{0x1, 0x2, 0x3}, &context.Headers{
SrcPort: context.TestPort,
DstPort: c.Port,
Flags: header.TCPFlagAck,
SeqNum: seqnum.Value(790),
AckNum: c.IRS.Add(1),
RcvWnd: 30000,
})
tcpbuf := vv.First()[header.IPv4MinimumSize:]
// Overwrite a byte in the payload which should cause checksum
// verification to fail.
tcpbuf[(tcpbuf[header.TCPDataOffset]>>4)*4] = 0x4
c.SendSegment(vv)
if got := stats.TCP.ChecksumErrors.Value(); got != want {
t.Errorf("got stats.TCP.ChecksumErrors.Value() = %d, want = %d", got, want)
}
}
func TestReceivedSegmentQueuing(t *testing.T) {
// This test sends 200 segments containing a few bytes each to an
// endpoint and checks that they're all received and acknowledged by
+1 -1
View File
@@ -640,7 +640,7 @@ func sendUDP(r *stack.Route, data buffer.VectorisedView, localPort, remotePort u
})
// Only calculate the checksum if offloading isn't supported.
if r.Capabilities()&stack.CapabilityChecksumOffload == 0 {
if r.Capabilities()&stack.CapabilityTXChecksumOffload == 0 {
xsum := r.PseudoHeaderChecksum(ProtocolNumber, length)
for _, v := range data.Views() {
xsum = header.Checksum(v, xsum)
+1
View File
@@ -142,6 +142,7 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct
Address: mac,
PacketDispatchMode: fdbased.PacketMMap,
GSOMaxSize: link.GSOMaxSize,
RXChecksumOffload: true,
})
log.Infof("Enabling interface %q with id %d on addresses %+v (%v)", link.Name, nicID, link.Addresses, mac)