mirror of
https://github.com/soypat/lneto.git
synced 2026-08-08 17:03:40 +00:00
add TCP options, more validation
This commit is contained in:
+62
-21
@@ -2,34 +2,69 @@ package lneto
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/soypat/tseq/lneto/tcp"
|
||||
)
|
||||
|
||||
func NewEthFrame(buf []byte) EthFrame {
|
||||
return EthFrame{buf: mustBufferFrameLen(buf, sizeHeaderEthNoVLAN, "Ethernet")}
|
||||
}
|
||||
func NewARPFrame(buf []byte) ARPFrame {
|
||||
return ARPFrame{buf: mustBufferFrameLen(buf, sizeHeaderARPv4, "ARPv4")}
|
||||
}
|
||||
func NewIPv4Frame(buf []byte) IPv4Frame {
|
||||
return IPv4Frame{buf: mustBufferFrameLen(buf, sizeHeaderIPv4, "IPv4")}
|
||||
}
|
||||
func NewIPv6Frame(buf []byte) IPv6Frame {
|
||||
return IPv6Frame{buf: mustBufferFrameLen(buf, sizeHeaderIPv6, "IPv6")}
|
||||
}
|
||||
func NewTCPFrame(buf []byte) TCPFrame {
|
||||
return TCPFrame{buf: mustBufferFrameLen(buf, sizeHeaderIPv4, "TCP")}
|
||||
}
|
||||
func NewUDPFrame(buf []byte) UDPFrame {
|
||||
return UDPFrame{buf: mustBufferFrameLen(buf, sizeHeaderUDP, "UDP")}
|
||||
// NewEthFrame returns a EthFrame with data set to buf.
|
||||
// Users should still call [EthFrame.ValidateSize] before working
|
||||
// with payload/options of frames to avoid panics.
|
||||
func NewEthFrame(buf []byte) (EthFrame, error) {
|
||||
if len(buf) < sizeHeaderEthNoVLAN {
|
||||
return EthFrame{buf: nil}, errors.New("ethernet packet too short")
|
||||
}
|
||||
return EthFrame{buf: buf}, nil
|
||||
}
|
||||
|
||||
func mustBufferFrameLen(b []byte, minLen int, name string) []byte {
|
||||
if len(b) < minLen {
|
||||
panic(name + " frame too short")
|
||||
// NewARPFrame returns a ARPFrame with data set to buf.
|
||||
// Users should still call [ARPFrame.ValidateSize] before working
|
||||
// with payload/options of frames to avoid panics.
|
||||
func NewARPFrame(buf []byte) (ARPFrame, error) {
|
||||
if len(buf) < sizeHeaderARPv4 {
|
||||
return ARPFrame{buf: nil}, errors.New("ARP packet too short")
|
||||
}
|
||||
return b
|
||||
return ARPFrame{buf: buf}, nil
|
||||
}
|
||||
|
||||
// NewIPv4Frame returns a new IPv4Frame with data set to buf.
|
||||
// Users should still call [IPv4Frame.ValidateSize] before working
|
||||
// with payload/options of frames to avoid panics.
|
||||
func NewIPv4Frame(buf []byte) (IPv4Frame, error) {
|
||||
if len(buf) < sizeHeaderIPv4 {
|
||||
return IPv4Frame{buf: nil}, errors.New("IPv4 packet too short")
|
||||
}
|
||||
return IPv4Frame{buf: buf}, nil
|
||||
}
|
||||
|
||||
// NewIPv6Frame returns a new IPv6Frame with data set to buf.
|
||||
// Users should still call [IPv6Frame.ValidateSize] before working
|
||||
// with payload/options of frames to avoid panics.
|
||||
func NewIPv6Frame(buf []byte) (IPv6Frame, error) {
|
||||
if len(buf) < sizeHeaderIPv6 {
|
||||
return IPv6Frame{buf: nil}, errors.New("IPv6 packet too short")
|
||||
}
|
||||
return IPv6Frame{buf: buf}, nil
|
||||
}
|
||||
|
||||
// NewTCPFrame returns a new TCPFrame with data set to buf.
|
||||
// Users should still call [TCPFrame.ValidateSize] before working
|
||||
// with payload/options of frames to avoid panics.
|
||||
func NewTCPFrame(buf []byte) (TCPFrame, error) {
|
||||
if len(buf) < sizeHeaderTCP {
|
||||
return TCPFrame{buf: nil}, errors.New("TCP packet too short")
|
||||
}
|
||||
return TCPFrame{buf: buf}, nil
|
||||
}
|
||||
|
||||
// NewUDPFrame returns a new UDPFrame with data set to buf.
|
||||
// Users should still call [UDPFrame.ValidateSize] before working
|
||||
// with payload/options of frames to avoid panics.
|
||||
func NewUDPFrame(buf []byte) (UDPFrame, error) {
|
||||
if len(buf) < sizeHeaderUDP {
|
||||
return UDPFrame{buf: buf}, errors.New("UDP packet too short")
|
||||
}
|
||||
return UDPFrame{buf: buf}, nil
|
||||
}
|
||||
|
||||
// EthFrame represents a Ethernet frame without including a preamble. The first byte is start of destination MAC address.
|
||||
@@ -512,6 +547,12 @@ func (tfrm TCPFrame) Payload() []byte {
|
||||
return tfrm.buf[tfrm.HeaderLength():]
|
||||
}
|
||||
|
||||
// Options returns the TCP option buffer portion of the frame. The returned slice may be zero length.
|
||||
// Be sure to call [TCPFrame.ValidateSize] beforehand to avoid panic.
|
||||
func (tfrm TCPFrame) Options() []byte {
|
||||
return tfrm.buf[sizeHeaderTCP:tfrm.HeaderLength()]
|
||||
}
|
||||
|
||||
type UDPFrame struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
+111
-1
@@ -1,12 +1,15 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=State -linecomment -output stringers.go .
|
||||
//go:generate stringer -type=State,OptionKind -linecomment -output stringers.go .
|
||||
|
||||
// Segment represents an incoming/outgoing TCP segment in the sequence space.
|
||||
type Segment struct {
|
||||
@@ -242,3 +245,110 @@ func (s State) IsSynchronized() bool {
|
||||
func (s State) isOpen() bool {
|
||||
return s != StateClosed && s != StateTimeWait // TODO: is this api ok?
|
||||
}
|
||||
|
||||
type OptionKind uint8
|
||||
|
||||
const (
|
||||
OptEnd OptionKind = iota // end of option list
|
||||
OptNop // no-operation
|
||||
OptMaxSegmentSize // maximum segment size
|
||||
OptWindowScale // window scale
|
||||
OptSACKPermitted // SACK permitted
|
||||
OptSACK // SACK
|
||||
OptEcho // echo(obsolete)
|
||||
optEchoReply // echo reply(obsolete)
|
||||
OptTimestamps // timestamps
|
||||
optPOCP // partial order connection permitted(obsolete)
|
||||
optPOSP // partial order service profile(obsolete)
|
||||
optCC // CC(obsolete)
|
||||
optCCnew // CC.new(obsolete)
|
||||
optCCecho // CC.echo(obsolete)
|
||||
optACR // alternate checksum request(obsolete)
|
||||
optACD // alternate checksum data(obsolete)
|
||||
optSkeeter // skeeter
|
||||
optBubba // bubba
|
||||
OptTrailerChecksum // trailer checksum
|
||||
optMD5Signature // MD5 signature(obsolete)
|
||||
OptSCPSCapabilities // SCPS capabilities
|
||||
OptSNA // selective negative acks
|
||||
OptRecordBoundaries // record boundaries
|
||||
OptCorruptionExperienced // corruption experienced
|
||||
OptSNAP // SNAP
|
||||
OptUnassigned // unassigned
|
||||
OptCompressionFilter // compression filter
|
||||
OptQuickStartResponse // quick-start response
|
||||
OptUserTimeout // user timeout or unauthorized use
|
||||
OptAuthetication // Authentication TCP-AO
|
||||
OptMultipath // multipath TCP
|
||||
)
|
||||
|
||||
const (
|
||||
OptFastOpenCookie OptionKind = 34 // fast open cookie
|
||||
OptEncryptionNegotiation OptionKind = 69 // encryption negotiation
|
||||
OptAccurateECN0 OptionKind = 172 // accurate ECN order 0
|
||||
OptAccurateECN1 OptionKind = 174 // accurate ECN order 1
|
||||
)
|
||||
|
||||
// IsObsolete returns true if option considered obsolete by newer TCP specifications.
|
||||
func (kind OptionKind) IsObsolete() bool {
|
||||
if kind.IsDefined() {
|
||||
return strings.HasSuffix(kind.String(), "(obsolete)")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDefined returns true if the option is a known unreserved option kind.
|
||||
func (kind OptionKind) IsDefined() bool {
|
||||
return kind <= 30 || kind == 34 || kind == 69 || kind == 172 || kind == 174
|
||||
}
|
||||
|
||||
type OptionParser struct {
|
||||
SkipSizeValidation bool
|
||||
SkipObsolete bool
|
||||
}
|
||||
|
||||
func (op *OptionParser) ForEachOption(opts []byte, fn func(OptionKind, []byte) error) error {
|
||||
off := 0
|
||||
skipSizeValidation := op.SkipSizeValidation
|
||||
skipObsolete := op.SkipObsolete
|
||||
for off < len(opts) && opts[off] != 0 {
|
||||
kind := OptionKind(opts[off])
|
||||
off++
|
||||
if kind == OptNop {
|
||||
continue
|
||||
}
|
||||
if len(opts[off:]) < 2 {
|
||||
return errors.New("short TCP options")
|
||||
}
|
||||
size := int(opts[off])
|
||||
off++
|
||||
if len(opts[off:]) < size {
|
||||
return fmt.Errorf("option %q length %d exceeds buffer size %d", kind.String(), size, len(opts[off:]))
|
||||
}
|
||||
|
||||
if !skipSizeValidation {
|
||||
expectSize := -1
|
||||
switch kind {
|
||||
case OptTimestamps:
|
||||
expectSize = 10
|
||||
case OptMaxSegmentSize, OptUserTimeout:
|
||||
expectSize = 4
|
||||
case OptWindowScale:
|
||||
expectSize = 3
|
||||
case OptSACKPermitted:
|
||||
expectSize = 2
|
||||
}
|
||||
if expectSize != -1 && size != expectSize {
|
||||
return fmt.Errorf("bad TCP option %q size want %d got %d", kind.String(), expectSize, opts[off])
|
||||
}
|
||||
}
|
||||
if skipObsolete && kind.IsObsolete() {
|
||||
err := fn(kind, opts[off:off+size])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
off += size
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+70
-1
@@ -1,4 +1,4 @@
|
||||
// Code generated by "stringer -type=State -linecomment -output stringers.go ."; DO NOT EDIT.
|
||||
// Code generated by "stringer -type=State,OptionKind -linecomment -output stringers.go ."; DO NOT EDIT.
|
||||
|
||||
package tcp
|
||||
|
||||
@@ -31,3 +31,72 @@ func (i State) String() string {
|
||||
}
|
||||
return _State_name[_State_index[i]:_State_index[i+1]]
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[OptEnd-0]
|
||||
_ = x[OptNop-1]
|
||||
_ = x[OptMaxSegmentSize-2]
|
||||
_ = x[OptWindowScale-3]
|
||||
_ = x[OptSACKPermitted-4]
|
||||
_ = x[OptSACK-5]
|
||||
_ = x[OptEcho-6]
|
||||
_ = x[optEchoReply-7]
|
||||
_ = x[OptTimestamps-8]
|
||||
_ = x[optPOCP-9]
|
||||
_ = x[optPOSP-10]
|
||||
_ = x[optCC-11]
|
||||
_ = x[optCCnew-12]
|
||||
_ = x[optCCecho-13]
|
||||
_ = x[optACR-14]
|
||||
_ = x[optACD-15]
|
||||
_ = x[optSkeeter-16]
|
||||
_ = x[optBubba-17]
|
||||
_ = x[OptTrailerChecksum-18]
|
||||
_ = x[optMD5Signature-19]
|
||||
_ = x[OptSCPSCapabilities-20]
|
||||
_ = x[OptSNA-21]
|
||||
_ = x[OptRecordBoundaries-22]
|
||||
_ = x[OptCorruptionExperienced-23]
|
||||
_ = x[OptSNAP-24]
|
||||
_ = x[OptUnassigned-25]
|
||||
_ = x[OptCompressionFilter-26]
|
||||
_ = x[OptQuickStartResponse-27]
|
||||
_ = x[OptUserTimeout-28]
|
||||
_ = x[OptAuthetication-29]
|
||||
_ = x[OptMultipath-30]
|
||||
_ = x[OptFastOpenCookie-34]
|
||||
_ = x[OptEncryptionNegotiation-69]
|
||||
_ = x[OptAccurateECN0-172]
|
||||
_ = x[OptAccurateECN1-174]
|
||||
}
|
||||
|
||||
const (
|
||||
_OptionKind_name_0 = "end of option listno-operationmaximum segment sizewindow scaleSACK permittedSACKecho(obsolete)echo reply(obsolete)timestampspartial order connection permitted(obsolete)partial order service profile(obsolete)CC(obsolete)CC.new(obsolete)CC.echo(obsolete)alternate checksum request(obsolete)alternate checksum data(obsolete)skeeterbubbatrailer checksumMD5 signature(obsolete)SCPS capabilitiesselective negative acksrecord boundariescorruption experiencedSNAPunassignedcompression filterquick-start responseuser timeout or unauthorized useAuthentication TCP-AOmultipath TCP"
|
||||
_OptionKind_name_1 = "fast open cookie"
|
||||
_OptionKind_name_2 = "encryption negotiation"
|
||||
_OptionKind_name_3 = "accurate ECN order 0"
|
||||
_OptionKind_name_4 = "accurate ECN order 1"
|
||||
)
|
||||
|
||||
var (
|
||||
_OptionKind_index_0 = [...]uint16{0, 18, 30, 50, 62, 76, 80, 94, 114, 124, 168, 207, 219, 235, 252, 288, 321, 328, 333, 349, 372, 389, 412, 429, 451, 455, 465, 483, 503, 535, 556, 569}
|
||||
)
|
||||
|
||||
func (i OptionKind) String() string {
|
||||
switch {
|
||||
case i <= 30:
|
||||
return _OptionKind_name_0[_OptionKind_index_0[i]:_OptionKind_index_0[i+1]]
|
||||
case i == 34:
|
||||
return _OptionKind_name_1
|
||||
case i == 69:
|
||||
return _OptionKind_name_2
|
||||
case i == 172:
|
||||
return _OptionKind_name_3
|
||||
case i == 174:
|
||||
return _OptionKind_name_4
|
||||
default:
|
||||
return "OptionKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package lneto
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errShortEth = errors.New("ethernet length exceeds frame")
|
||||
errShortVLAN = errors.New("ethernet length too short for VLAN")
|
||||
errShortUDP = errors.New("UDP length exceeds frame")
|
||||
errBadUDPLen = errors.New("UDP length invalid")
|
||||
errShortIPv4 = errors.New("IPv4 total length exceeds frame")
|
||||
@@ -13,6 +15,18 @@ var (
|
||||
errBadTCPOff = errors.New("TCP offset invalid")
|
||||
)
|
||||
|
||||
// ValidateSize checks the frame's size fields and compares with the actual buffer
|
||||
// the frame. It returns a non-nil error on finding an inconsistency.
|
||||
func (efrm EthFrame) ValidateSize() error {
|
||||
sz := efrm.EtherTypeOrSize()
|
||||
if sz.IsSize() && len(efrm.buf) < int(sz) {
|
||||
return errShortEth
|
||||
} else if sz == EtherTypeVLAN && len(efrm.buf) < 18 {
|
||||
return errShortVLAN
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSize checks the frame's size fields and compares with the actual buffer
|
||||
// the frame. It returns a non-nil error on finding an inconsistency.
|
||||
func (afrm ARPFrame) ValidateSize() error {
|
||||
|
||||
Reference in New Issue
Block a user