mirror of
https://github.com/soypat/lneto.git
synced 2026-08-22 15:39:06 +00:00
add TCP options, more validation
This commit is contained in:
+62
-21
@@ -2,34 +2,69 @@ package lneto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/soypat/tseq/lneto/tcp"
|
"github.com/soypat/tseq/lneto/tcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewEthFrame(buf []byte) EthFrame {
|
// NewEthFrame returns a EthFrame with data set to buf.
|
||||||
return EthFrame{buf: mustBufferFrameLen(buf, sizeHeaderEthNoVLAN, "Ethernet")}
|
// Users should still call [EthFrame.ValidateSize] before working
|
||||||
}
|
// with payload/options of frames to avoid panics.
|
||||||
func NewARPFrame(buf []byte) ARPFrame {
|
func NewEthFrame(buf []byte) (EthFrame, error) {
|
||||||
return ARPFrame{buf: mustBufferFrameLen(buf, sizeHeaderARPv4, "ARPv4")}
|
if len(buf) < sizeHeaderEthNoVLAN {
|
||||||
}
|
return EthFrame{buf: nil}, errors.New("ethernet packet too short")
|
||||||
func NewIPv4Frame(buf []byte) IPv4Frame {
|
}
|
||||||
return IPv4Frame{buf: mustBufferFrameLen(buf, sizeHeaderIPv4, "IPv4")}
|
return EthFrame{buf: buf}, nil
|
||||||
}
|
|
||||||
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")}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustBufferFrameLen(b []byte, minLen int, name string) []byte {
|
// NewARPFrame returns a ARPFrame with data set to buf.
|
||||||
if len(b) < minLen {
|
// Users should still call [ARPFrame.ValidateSize] before working
|
||||||
panic(name + " frame too short")
|
// 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.
|
// 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():]
|
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 {
|
type UDPFrame struct {
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-1
@@ -1,12 +1,15 @@
|
|||||||
package tcp
|
package tcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"unsafe"
|
"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.
|
// Segment represents an incoming/outgoing TCP segment in the sequence space.
|
||||||
type Segment struct {
|
type Segment struct {
|
||||||
@@ -242,3 +245,110 @@ func (s State) IsSynchronized() bool {
|
|||||||
func (s State) isOpen() bool {
|
func (s State) isOpen() bool {
|
||||||
return s != StateClosed && s != StateTimeWait // TODO: is this api ok?
|
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
|
package tcp
|
||||||
|
|
||||||
@@ -31,3 +31,72 @@ func (i State) String() string {
|
|||||||
}
|
}
|
||||||
return _State_name[_State_index[i]:_State_index[i+1]]
|
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"
|
import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
errShortEth = errors.New("ethernet length exceeds frame")
|
||||||
|
errShortVLAN = errors.New("ethernet length too short for VLAN")
|
||||||
errShortUDP = errors.New("UDP length exceeds frame")
|
errShortUDP = errors.New("UDP length exceeds frame")
|
||||||
errBadUDPLen = errors.New("UDP length invalid")
|
errBadUDPLen = errors.New("UDP length invalid")
|
||||||
errShortIPv4 = errors.New("IPv4 total length exceeds frame")
|
errShortIPv4 = errors.New("IPv4 total length exceeds frame")
|
||||||
@@ -13,6 +15,18 @@ var (
|
|||||||
errBadTCPOff = errors.New("TCP offset invalid")
|
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
|
// 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.
|
// the frame. It returns a non-nil error on finding an inconsistency.
|
||||||
func (afrm ARPFrame) ValidateSize() error {
|
func (afrm ARPFrame) ValidateSize() error {
|
||||||
|
|||||||
Reference in New Issue
Block a user