Make gtcpip public for external use
This commit is contained in:
parent
f8e7794cc2
commit
a9027729be
42 changed files with 45 additions and 57 deletions
|
|
@ -1,722 +0,0 @@
|
|||
// Copyright 2018 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip"
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/seqnum"
|
||||
|
||||
"github.com/google/btree"
|
||||
)
|
||||
|
||||
// These constants are the offsets of the respective fields in the TCP header.
|
||||
const (
|
||||
TCPSrcPortOffset = 0
|
||||
TCPDstPortOffset = 2
|
||||
TCPSeqNumOffset = 4
|
||||
TCPAckNumOffset = 8
|
||||
TCPDataOffset = 12
|
||||
TCPFlagsOffset = 13
|
||||
TCPWinSizeOffset = 14
|
||||
TCPChecksumOffset = 16
|
||||
TCPUrgentPtrOffset = 18
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxWndScale is maximum allowed window scaling, as described in
|
||||
// RFC 1323, section 2.3, page 11.
|
||||
MaxWndScale = 14
|
||||
|
||||
// TCPMaxSACKBlocks is the maximum number of SACK blocks that can
|
||||
// be encoded in a TCP option field.
|
||||
TCPMaxSACKBlocks = 4
|
||||
)
|
||||
|
||||
// TCPFlags is the dedicated type for TCP flags.
|
||||
type TCPFlags uint8
|
||||
|
||||
// Intersects returns true iff there are flags common to both f and o.
|
||||
func (f TCPFlags) Intersects(o TCPFlags) bool {
|
||||
return f&o != 0
|
||||
}
|
||||
|
||||
// Contains returns true iff all the flags in o are contained within f.
|
||||
func (f TCPFlags) Contains(o TCPFlags) bool {
|
||||
return f&o == o
|
||||
}
|
||||
|
||||
// String implements Stringer.String.
|
||||
func (f TCPFlags) String() string {
|
||||
flagsStr := []byte("FSRPAUEC")
|
||||
for i := range flagsStr {
|
||||
if f&(1<<uint(i)) == 0 {
|
||||
flagsStr[i] = ' '
|
||||
}
|
||||
}
|
||||
return string(flagsStr)
|
||||
}
|
||||
|
||||
// Flags that may be set in a TCP segment.
|
||||
const (
|
||||
TCPFlagFin TCPFlags = 1 << iota
|
||||
TCPFlagSyn
|
||||
TCPFlagRst
|
||||
TCPFlagPsh
|
||||
TCPFlagAck
|
||||
TCPFlagUrg
|
||||
TCPFlagEce
|
||||
TCPFlagCwr
|
||||
)
|
||||
|
||||
// Options that may be present in a TCP segment.
|
||||
const (
|
||||
TCPOptionEOL = 0
|
||||
TCPOptionNOP = 1
|
||||
TCPOptionMSS = 2
|
||||
TCPOptionWS = 3
|
||||
TCPOptionTS = 8
|
||||
TCPOptionSACKPermitted = 4
|
||||
TCPOptionSACK = 5
|
||||
)
|
||||
|
||||
// Option Lengths.
|
||||
const (
|
||||
TCPOptionMSSLength = 4
|
||||
TCPOptionTSLength = 10
|
||||
TCPOptionWSLength = 3
|
||||
TCPOptionSackPermittedLength = 2
|
||||
)
|
||||
|
||||
// TCPFields contains the fields of a TCP packet. It is used to describe the
|
||||
// fields of a packet that needs to be encoded.
|
||||
type TCPFields struct {
|
||||
// SrcPort is the "source port" field of a TCP packet.
|
||||
SrcPort uint16
|
||||
|
||||
// DstPort is the "destination port" field of a TCP packet.
|
||||
DstPort uint16
|
||||
|
||||
// SeqNum is the "sequence number" field of a TCP packet.
|
||||
SeqNum uint32
|
||||
|
||||
// AckNum is the "acknowledgement number" field of a TCP packet.
|
||||
AckNum uint32
|
||||
|
||||
// DataOffset is the "data offset" field of a TCP packet. It is the length of
|
||||
// the TCP header in bytes.
|
||||
DataOffset uint8
|
||||
|
||||
// Flags is the "flags" field of a TCP packet.
|
||||
Flags TCPFlags
|
||||
|
||||
// WindowSize is the "window size" field of a TCP packet.
|
||||
WindowSize uint16
|
||||
|
||||
// Checksum is the "checksum" field of a TCP packet.
|
||||
Checksum uint16
|
||||
|
||||
// UrgentPointer is the "urgent pointer" field of a TCP packet.
|
||||
UrgentPointer uint16
|
||||
}
|
||||
|
||||
// TCPSynOptions is used to return the parsed TCP Options in a syn
|
||||
// segment.
|
||||
//
|
||||
// +stateify savable
|
||||
type TCPSynOptions struct {
|
||||
// MSS is the maximum segment size provided by the peer in the SYN.
|
||||
MSS uint16
|
||||
|
||||
// WS is the window scale option provided by the peer in the SYN.
|
||||
//
|
||||
// Set to -1 if no window scale option was provided.
|
||||
WS int
|
||||
|
||||
// TS is true if the timestamp option was provided in the syn/syn-ack.
|
||||
TS bool
|
||||
|
||||
// TSVal is the value of the TSVal field in the timestamp option.
|
||||
TSVal uint32
|
||||
|
||||
// TSEcr is the value of the TSEcr field in the timestamp option.
|
||||
TSEcr uint32
|
||||
|
||||
// SACKPermitted is true if the SACK option was provided in the SYN/SYN-ACK.
|
||||
SACKPermitted bool
|
||||
|
||||
// Flags if specified are set on the outgoing SYN. The SYN flag is
|
||||
// always set.
|
||||
Flags TCPFlags
|
||||
}
|
||||
|
||||
// SACKBlock represents a single contiguous SACK block.
|
||||
//
|
||||
// +stateify savable
|
||||
type SACKBlock struct {
|
||||
// Start indicates the lowest sequence number in the block.
|
||||
Start seqnum.Value
|
||||
|
||||
// End indicates the sequence number immediately following the last
|
||||
// sequence number of this block.
|
||||
End seqnum.Value
|
||||
}
|
||||
|
||||
// Less returns true if r.Start < b.Start.
|
||||
func (r SACKBlock) Less(b btree.Item) bool {
|
||||
return r.Start.LessThan(b.(SACKBlock).Start)
|
||||
}
|
||||
|
||||
// Contains returns true if b is completely contained in r.
|
||||
func (r SACKBlock) Contains(b SACKBlock) bool {
|
||||
return r.Start.LessThanEq(b.Start) && b.End.LessThanEq(r.End)
|
||||
}
|
||||
|
||||
// TCPOptions are used to parse and cache the TCP segment options for a non
|
||||
// syn/syn-ack segment.
|
||||
//
|
||||
// +stateify savable
|
||||
type TCPOptions struct {
|
||||
// TS is true if the TimeStamp option is enabled.
|
||||
TS bool
|
||||
|
||||
// TSVal is the value in the TSVal field of the segment.
|
||||
TSVal uint32
|
||||
|
||||
// TSEcr is the value in the TSEcr field of the segment.
|
||||
TSEcr uint32
|
||||
|
||||
// SACKBlocks are the SACK blocks specified in the segment.
|
||||
SACKBlocks []SACKBlock
|
||||
}
|
||||
|
||||
// TCP represents a TCP header stored in a byte array.
|
||||
type TCP []byte
|
||||
|
||||
const (
|
||||
// TCPMinimumSize is the minimum size of a valid TCP packet.
|
||||
TCPMinimumSize = 20
|
||||
|
||||
// TCPOptionsMaximumSize is the maximum size of TCP options.
|
||||
TCPOptionsMaximumSize = 40
|
||||
|
||||
// TCPHeaderMaximumSize is the maximum header size of a TCP packet.
|
||||
TCPHeaderMaximumSize = TCPMinimumSize + TCPOptionsMaximumSize
|
||||
|
||||
// TCPTotalHeaderMaximumSize is the maximum size of headers from all layers in
|
||||
// a TCP packet. It analogous to MAX_TCP_HEADER in Linux.
|
||||
//
|
||||
// TODO(b/319936470): Investigate why this needs to be at least 140 bytes. In
|
||||
// Linux this value is at least 160, but in theory we should be able to use
|
||||
// 138. In practice anything less than 140 starts to break GSO on gVNIC
|
||||
// hardware.
|
||||
TCPTotalHeaderMaximumSize = 160
|
||||
|
||||
// TCPProtocolNumber is TCP's transport protocol number.
|
||||
TCPProtocolNumber tcpip.TransportProtocolNumber = 6
|
||||
|
||||
// TCPMinimumMSS is the minimum acceptable value for MSS. This is the
|
||||
// same as the value TCP_MIN_MSS defined net/tcp.h.
|
||||
TCPMinimumMSS = IPv4MaximumHeaderSize + TCPHeaderMaximumSize + MinIPFragmentPayloadSize - IPv4MinimumSize - TCPMinimumSize
|
||||
|
||||
// TCPMinimumSendMSS is the minimum value for MSS in a sender. This is the
|
||||
// same as the value TCP_MIN_SND_MSS in net/tcp.h.
|
||||
TCPMinimumSendMSS = TCPOptionsMaximumSize + MinIPFragmentPayloadSize
|
||||
|
||||
// TCPMaximumMSS is the maximum acceptable value for MSS.
|
||||
TCPMaximumMSS = 0xffff
|
||||
|
||||
// TCPDefaultMSS is the MSS value that should be used if an MSS option
|
||||
// is not received from the peer. It's also the value returned by
|
||||
// TCP_MAXSEG option for a socket in an unconnected state.
|
||||
//
|
||||
// Per RFC 1122, page 85: "If an MSS option is not received at
|
||||
// connection setup, TCP MUST assume a default send MSS of 536."
|
||||
TCPDefaultMSS = 536
|
||||
)
|
||||
|
||||
// SourcePort returns the "source port" field of the TCP header.
|
||||
func (b TCP) SourcePort() uint16 {
|
||||
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[TCPDstPortOffset:])
|
||||
}
|
||||
|
||||
// SequenceNumber returns the "sequence number" field of the TCP header.
|
||||
func (b TCP) SequenceNumber() uint32 {
|
||||
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[TCPAckNumOffset:])
|
||||
}
|
||||
|
||||
// DataOffset returns the "data offset" field of the TCP header. The return
|
||||
// value is the length of the TCP header in bytes.
|
||||
func (b TCP) DataOffset() uint8 {
|
||||
return (b[TCPDataOffset] >> 4) * 4
|
||||
}
|
||||
|
||||
// Payload returns the data in the TCP packet.
|
||||
func (b TCP) Payload() []byte {
|
||||
return b[b.DataOffset():]
|
||||
}
|
||||
|
||||
// Flags returns the flags field of the TCP header.
|
||||
func (b TCP) Flags() TCPFlags {
|
||||
return TCPFlags(b[TCPFlagsOffset])
|
||||
}
|
||||
|
||||
// WindowSize returns the "window size" field of the TCP header.
|
||||
func (b TCP) WindowSize() uint16 {
|
||||
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[TCPChecksumOffset:])
|
||||
}
|
||||
|
||||
// UrgentPointer returns the "urgent pointer" field of the TCP header.
|
||||
func (b TCP) UrgentPointer() uint16 {
|
||||
return binary.BigEndian.Uint16(b[TCPUrgentPtrOffset:])
|
||||
}
|
||||
|
||||
// SetSourcePort sets the "source port" field of the TCP header.
|
||||
func (b TCP) SetSourcePort(port uint16) {
|
||||
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[TCPDstPortOffset:], port)
|
||||
}
|
||||
|
||||
// SetChecksum sets the checksum field of the TCP header.
|
||||
func (b TCP) SetChecksum(xsum uint16) {
|
||||
checksum.Put(b[TCPChecksumOffset:], xsum)
|
||||
}
|
||||
|
||||
// SetDataOffset sets the data offset field of the TCP header. headerLen should
|
||||
// be the length of the TCP header in bytes.
|
||||
func (b TCP) SetDataOffset(headerLen uint8) {
|
||||
b[TCPDataOffset] = (headerLen / 4) << 4
|
||||
}
|
||||
|
||||
// SetSequenceNumber sets the sequence number field of the TCP header.
|
||||
func (b TCP) SetSequenceNumber(seqNum uint32) {
|
||||
binary.BigEndian.PutUint32(b[TCPSeqNumOffset:], seqNum)
|
||||
}
|
||||
|
||||
// SetAckNumber sets the ack number field of the TCP header.
|
||||
func (b TCP) SetAckNumber(ackNum uint32) {
|
||||
binary.BigEndian.PutUint32(b[TCPAckNumOffset:], ackNum)
|
||||
}
|
||||
|
||||
// SetFlags sets the flags field of the TCP header.
|
||||
func (b TCP) SetFlags(flags uint8) {
|
||||
b[TCPFlagsOffset] = flags
|
||||
}
|
||||
|
||||
// SetWindowSize sets the window size field of the TCP header.
|
||||
func (b TCP) SetWindowSize(rcvwnd uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPWinSizeOffset:], rcvwnd)
|
||||
}
|
||||
|
||||
// SetUrgentPointer sets the window size field of the TCP header.
|
||||
func (b TCP) SetUrgentPointer(urgentPointer uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPUrgentPtrOffset:], urgentPointer)
|
||||
}
|
||||
|
||||
// CalculateChecksum calculates the checksum of the TCP segment.
|
||||
// partialChecksum is the checksum of the network-layer pseudo-header
|
||||
// and the checksum of the segment data.
|
||||
func (b TCP) CalculateChecksum(partialChecksum uint16) uint16 {
|
||||
// Calculate the rest of the checksum.
|
||||
// return checksum.Checksum(b[:b.DataOffset()], partialChecksum)
|
||||
xsum := checksum.Checksum(b[:TCPChecksumOffset], partialChecksum)
|
||||
xsum = checksum.Checksum(b[TCPChecksumOffset+2:b.DataOffset()], xsum)
|
||||
return xsum
|
||||
}
|
||||
|
||||
// IsChecksumValid returns true iff the TCP header's checksum is valid.
|
||||
func (b TCP) IsChecksumValid(src, dst tcpip.Address, payloadChecksum, payloadLength uint16) bool {
|
||||
xsum := PseudoHeaderChecksum(TCPProtocolNumber, src.AsSlice(), dst.AsSlice(), uint16(b.DataOffset())+payloadLength)
|
||||
xsum = checksum.Combine(xsum, payloadChecksum)
|
||||
// return b.CalculateChecksum(xsum) == 0xffff
|
||||
return checksum.Checksum(b[:b.DataOffset()], xsum) == 0xffff
|
||||
}
|
||||
|
||||
// Options returns a slice that holds the unparsed TCP options in the segment.
|
||||
func (b TCP) Options() []byte {
|
||||
return b[TCPMinimumSize:b.DataOffset()]
|
||||
}
|
||||
|
||||
// ParsedOptions returns a TCPOptions structure which parses and caches the TCP
|
||||
// option values in the TCP segment. NOTE: Invoking this function repeatedly is
|
||||
// expensive as it reparses the options on each invocation.
|
||||
func (b TCP) ParsedOptions() TCPOptions {
|
||||
return ParseTCPOptions(b.Options())
|
||||
}
|
||||
|
||||
func (b TCP) encodeSubset(seq, ack uint32, flags TCPFlags, rcvwnd uint16) {
|
||||
binary.BigEndian.PutUint32(b[TCPSeqNumOffset:], seq)
|
||||
binary.BigEndian.PutUint32(b[TCPAckNumOffset:], ack)
|
||||
b[TCPFlagsOffset] = uint8(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)
|
||||
b.SetSourcePort(t.SrcPort)
|
||||
b.SetDestinationPort(t.DstPort)
|
||||
b.SetDataOffset(t.DataOffset)
|
||||
b.SetChecksum(t.Checksum)
|
||||
b.SetUrgentPointer(t.UrgentPointer)
|
||||
}
|
||||
|
||||
// EncodePartial updates a subset of the fields of the TCP header. It is useful
|
||||
// in cases when similar segments are produced.
|
||||
func (b TCP) EncodePartial(partialChecksum, length uint16, seqnum, acknum uint32, flags TCPFlags, rcvwnd uint16) {
|
||||
// Add the total length and "flags" field contributions to the checksum.
|
||||
// We don't use the flags field directly from the header because it's a
|
||||
// one-byte field with an odd offset, so it would be accounted for
|
||||
// incorrectly by the Checksum routine.
|
||||
tmp := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(tmp, length)
|
||||
binary.BigEndian.PutUint16(tmp[2:], uint16(flags))
|
||||
xsum := checksum.Checksum(tmp, partialChecksum)
|
||||
|
||||
// Encode the passed-in fields.
|
||||
b.encodeSubset(seqnum, acknum, flags, rcvwnd)
|
||||
|
||||
// Add the contributions of the passed-in fields to the checksum.
|
||||
xsum = checksum.Checksum(b[TCPSeqNumOffset:TCPSeqNumOffset+8], xsum)
|
||||
xsum = checksum.Checksum(b[TCPWinSizeOffset:TCPWinSizeOffset+2], xsum)
|
||||
|
||||
// Encode the checksum.
|
||||
b.SetChecksum(^xsum)
|
||||
}
|
||||
|
||||
// SetSourcePortWithChecksumUpdate implements ChecksummableTransport.
|
||||
func (b TCP) SetSourcePortWithChecksumUpdate(new uint16) {
|
||||
old := b.SourcePort()
|
||||
b.SetSourcePort(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
}
|
||||
|
||||
// SetDestinationPortWithChecksumUpdate implements ChecksummableTransport.
|
||||
func (b TCP) SetDestinationPortWithChecksumUpdate(new uint16) {
|
||||
old := b.DestinationPort()
|
||||
b.SetDestinationPort(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
}
|
||||
|
||||
// UpdateChecksumPseudoHeaderAddress implements ChecksummableTransport.
|
||||
func (b TCP) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address, fullChecksum bool) {
|
||||
xsum := b.Checksum()
|
||||
if fullChecksum {
|
||||
xsum = ^xsum
|
||||
}
|
||||
|
||||
xsum = checksumUpdate2ByteAlignedAddress(xsum, old, new)
|
||||
if fullChecksum {
|
||||
xsum = ^xsum
|
||||
}
|
||||
|
||||
b.SetChecksum(xsum)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
limit := len(opts)
|
||||
|
||||
synOpts := TCPSynOptions{
|
||||
// Per RFC 1122, page 85: "If an MSS option is not received at
|
||||
// connection setup, TCP MUST assume a default send MSS of 536."
|
||||
MSS: TCPDefaultMSS,
|
||||
// If no window scale option is specified, WS in options is
|
||||
// returned as -1; this is because the absence of the option
|
||||
// indicates that the we cannot use window scaling on the
|
||||
// receive end either.
|
||||
WS: -1,
|
||||
}
|
||||
|
||||
for i := 0; i < limit; {
|
||||
switch opts[i] {
|
||||
case TCPOptionEOL:
|
||||
i = limit
|
||||
case TCPOptionNOP:
|
||||
i++
|
||||
case TCPOptionMSS:
|
||||
if i+4 > limit || opts[i+1] != 4 {
|
||||
return synOpts
|
||||
}
|
||||
mss := uint16(opts[i+2])<<8 | uint16(opts[i+3])
|
||||
if mss == 0 {
|
||||
return synOpts
|
||||
}
|
||||
synOpts.MSS = max(mss, TCPMinimumSendMSS)
|
||||
i += 4
|
||||
|
||||
case TCPOptionWS:
|
||||
if i+3 > limit || opts[i+1] != 3 {
|
||||
return synOpts
|
||||
}
|
||||
ws := min(int(opts[i+2]), MaxWndScale)
|
||||
synOpts.WS = ws
|
||||
i += 3
|
||||
|
||||
case TCPOptionTS:
|
||||
if i+10 > limit || opts[i+1] != 10 {
|
||||
return synOpts
|
||||
}
|
||||
synOpts.TSVal = binary.BigEndian.Uint32(opts[i+2:])
|
||||
if isAck {
|
||||
// If the segment is a SYN-ACK then store the Timestamp Echo Reply
|
||||
// in the segment.
|
||||
synOpts.TSEcr = binary.BigEndian.Uint32(opts[i+6:])
|
||||
}
|
||||
synOpts.TS = true
|
||||
i += 10
|
||||
case TCPOptionSACKPermitted:
|
||||
if i+2 > limit || opts[i+1] != 2 {
|
||||
return synOpts
|
||||
}
|
||||
synOpts.SACKPermitted = true
|
||||
i += 2
|
||||
|
||||
default:
|
||||
// We don't recognize this option, just skip over it.
|
||||
if i+2 > limit {
|
||||
return synOpts
|
||||
}
|
||||
l := int(opts[i+1])
|
||||
// If the length is incorrect or if l+i overflows the
|
||||
// total options length then return false.
|
||||
if l < 2 || i+l > limit {
|
||||
return synOpts
|
||||
}
|
||||
i += l
|
||||
}
|
||||
}
|
||||
|
||||
return synOpts
|
||||
}
|
||||
|
||||
// ParseTCPOptions extracts and stores all known options in the provided byte
|
||||
// slice in a TCPOptions structure.
|
||||
func ParseTCPOptions(b []byte) TCPOptions {
|
||||
opts := TCPOptions{}
|
||||
limit := len(b)
|
||||
for i := 0; i < limit; {
|
||||
switch b[i] {
|
||||
case TCPOptionEOL:
|
||||
i = limit
|
||||
case TCPOptionNOP:
|
||||
i++
|
||||
case TCPOptionTS:
|
||||
if i+10 > limit || (b[i+1] != 10) {
|
||||
return opts
|
||||
}
|
||||
opts.TS = true
|
||||
opts.TSVal = binary.BigEndian.Uint32(b[i+2:])
|
||||
opts.TSEcr = binary.BigEndian.Uint32(b[i+6:])
|
||||
i += 10
|
||||
case TCPOptionSACK:
|
||||
if i+2 > limit {
|
||||
// Malformed SACK block, just return and stop parsing.
|
||||
return opts
|
||||
}
|
||||
sackOptionLen := int(b[i+1])
|
||||
if i+sackOptionLen > limit || (sackOptionLen-2)%8 != 0 {
|
||||
// Malformed SACK block, just return and stop parsing.
|
||||
return opts
|
||||
}
|
||||
numBlocks := (sackOptionLen - 2) / 8
|
||||
opts.SACKBlocks = []SACKBlock{}
|
||||
for j := range numBlocks {
|
||||
start := binary.BigEndian.Uint32(b[i+2+j*8:])
|
||||
end := binary.BigEndian.Uint32(b[i+2+j*8+4:])
|
||||
opts.SACKBlocks = append(opts.SACKBlocks, SACKBlock{
|
||||
Start: seqnum.Value(start),
|
||||
End: seqnum.Value(end),
|
||||
})
|
||||
}
|
||||
i += sackOptionLen
|
||||
default:
|
||||
// We don't recognize this option, just skip over it.
|
||||
if i+2 > limit {
|
||||
return opts
|
||||
}
|
||||
l := int(b[i+1])
|
||||
// If the length is incorrect or if l+i overflows the
|
||||
// total options length then return false.
|
||||
if l < 2 || i+l > limit {
|
||||
return opts
|
||||
}
|
||||
i += l
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// EncodeMSSOption encodes the MSS TCP option with the provided MSS values in
|
||||
// the supplied buffer. If the provided buffer is not large enough then it just
|
||||
// returns without encoding anything. It returns the number of bytes written to
|
||||
// the provided buffer.
|
||||
func EncodeMSSOption(mss uint32, b []byte) int {
|
||||
if len(b) < TCPOptionMSSLength {
|
||||
return 0
|
||||
}
|
||||
b[0], b[1], b[2], b[3] = TCPOptionMSS, TCPOptionMSSLength, byte(mss>>8), byte(mss)
|
||||
return TCPOptionMSSLength
|
||||
}
|
||||
|
||||
// EncodeWSOption encodes the WS TCP option with the WS value in the
|
||||
// provided buffer. If the provided buffer is not large enough then it just
|
||||
// returns without encoding anything. It returns the number of bytes written to
|
||||
// the provided buffer.
|
||||
func EncodeWSOption(ws int, b []byte) int {
|
||||
if len(b) < TCPOptionWSLength {
|
||||
return 0
|
||||
}
|
||||
b[0], b[1], b[2] = TCPOptionWS, TCPOptionWSLength, uint8(ws)
|
||||
return int(b[1])
|
||||
}
|
||||
|
||||
// EncodeTSOption encodes the provided tsVal and tsEcr values as a TCP timestamp
|
||||
// option into the provided buffer. If the buffer is smaller than expected it
|
||||
// just returns without encoding anything. It returns the number of bytes
|
||||
// written to the provided buffer.
|
||||
func EncodeTSOption(tsVal, tsEcr uint32, b []byte) int {
|
||||
if len(b) < TCPOptionTSLength {
|
||||
return 0
|
||||
}
|
||||
b[0], b[1] = TCPOptionTS, TCPOptionTSLength
|
||||
binary.BigEndian.PutUint32(b[2:], tsVal)
|
||||
binary.BigEndian.PutUint32(b[6:], tsEcr)
|
||||
return int(b[1])
|
||||
}
|
||||
|
||||
// EncodeSACKPermittedOption encodes a SACKPermitted option into the provided
|
||||
// buffer. If the buffer is smaller than required it just returns without
|
||||
// encoding anything. It returns the number of bytes written to the provided
|
||||
// buffer.
|
||||
func EncodeSACKPermittedOption(b []byte) int {
|
||||
if len(b) < TCPOptionSackPermittedLength {
|
||||
return 0
|
||||
}
|
||||
|
||||
b[0], b[1] = TCPOptionSACKPermitted, TCPOptionSackPermittedLength
|
||||
return int(b[1])
|
||||
}
|
||||
|
||||
// EncodeSACKBlocks encodes the provided SACK blocks as a TCP SACK option block
|
||||
// in the provided slice. It tries to fit in as many blocks as possible based on
|
||||
// number of bytes available in the provided buffer. It returns the number of
|
||||
// bytes written to the provided buffer.
|
||||
func EncodeSACKBlocks(sackBlocks []SACKBlock, b []byte) int {
|
||||
if len(sackBlocks) == 0 {
|
||||
return 0
|
||||
}
|
||||
l := min(len(sackBlocks), TCPMaxSACKBlocks)
|
||||
if ll := (len(b) - 2) / 8; ll < l {
|
||||
l = ll
|
||||
}
|
||||
if l == 0 {
|
||||
// There is not enough space in the provided buffer to add
|
||||
// any SACK blocks.
|
||||
return 0
|
||||
}
|
||||
b[0] = TCPOptionSACK
|
||||
b[1] = byte(l*8 + 2)
|
||||
for i := 0; i < l; i++ {
|
||||
binary.BigEndian.PutUint32(b[i*8+2:], uint32(sackBlocks[i].Start))
|
||||
binary.BigEndian.PutUint32(b[i*8+6:], uint32(sackBlocks[i].End))
|
||||
}
|
||||
return int(b[1])
|
||||
}
|
||||
|
||||
// EncodeNOP adds an explicit NOP to the option list.
|
||||
func EncodeNOP(b []byte) int {
|
||||
if len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
b[0] = TCPOptionNOP
|
||||
return 1
|
||||
}
|
||||
|
||||
// AddTCPOptionPadding adds the required number of TCPOptionNOP to quad align
|
||||
// the option buffer. It adds padding bytes after the offset specified and
|
||||
// returns the number of padding bytes added. The passed in options slice
|
||||
// must have space for the padding bytes.
|
||||
func AddTCPOptionPadding(options []byte, offset int) int {
|
||||
paddingToAdd := -offset & 3
|
||||
// Now add any padding bytes that might be required to quad align the
|
||||
// options.
|
||||
for i := offset; i < offset+paddingToAdd; i++ {
|
||||
options[i] = TCPOptionNOP
|
||||
}
|
||||
return paddingToAdd
|
||||
}
|
||||
|
||||
// Acceptable checks if a segment that starts at segSeq and has length segLen is
|
||||
// "acceptable" for arriving in a receive window that starts at rcvNxt and ends
|
||||
// before rcvAcc, according to the table on page 26 and 69 of RFC 793.
|
||||
func Acceptable(segSeq seqnum.Value, segLen seqnum.Size, rcvNxt, rcvAcc seqnum.Value) bool {
|
||||
if rcvNxt == rcvAcc {
|
||||
return segLen == 0 && segSeq == rcvNxt
|
||||
}
|
||||
if segLen == 0 {
|
||||
// rcvWnd is incremented by 1 because that is Linux's behavior despite the
|
||||
// RFC.
|
||||
return segSeq.InRange(rcvNxt, rcvAcc.Add(1))
|
||||
}
|
||||
// Page 70 of RFC 793 allows packets that can be made "acceptable" by trimming
|
||||
// the payload, so we'll accept any payload that overlaps the receive window.
|
||||
// segSeq < rcvAcc is more correct according to RFC, however, Linux does it
|
||||
// differently, it uses segSeq <= rcvAcc, we'd want to keep the same behavior
|
||||
// as Linux.
|
||||
return rcvNxt.LessThan(segSeq.Add(segLen)) && segSeq.LessThanEq(rcvAcc)
|
||||
}
|
||||
|
||||
// TCPValid returns true if the pkt has a valid TCP header. It checks whether:
|
||||
// - The data offset is too small.
|
||||
// - The data offset is too large.
|
||||
// - The checksum is invalid.
|
||||
//
|
||||
// TCPValid corresponds to net/netfilter/nf_conntrack_proto_tcp.c:tcp_error.
|
||||
func TCPValid(hdr TCP, payloadChecksum func() uint16, payloadSize uint16, srcAddr, dstAddr tcpip.Address, skipChecksumValidation bool) (csum uint16, csumValid, ok bool) {
|
||||
if offset := int(hdr.DataOffset()); offset < TCPMinimumSize || offset > len(hdr) {
|
||||
return
|
||||
}
|
||||
|
||||
if skipChecksumValidation {
|
||||
csumValid = true
|
||||
} else {
|
||||
csum = hdr.Checksum()
|
||||
csumValid = hdr.IsChecksumValid(srcAddr, dstAddr, payloadChecksum(), payloadSize)
|
||||
}
|
||||
return csum, csumValid, true
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue