commit 8a186cfeb2d5b7d4270952250c9a1fc16ab19705 Author: Anastasios Svolis Date: Wed Dec 10 05:17:46 2025 +0200 First commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5d08732 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2019 Fedorenko Dmitrij + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f46a5b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +GOARCH := $(shell go env GOARCH) +GOOS := $(shell go env GOOS) + +GOLANGCI_LINT_VERSION := 1.16.0 +GOLANGCI_LINT_ARCHIVE_NAME := golangci-lint-${GOLANGCI_LINT_VERSION}-${GOOS}-${GOARCH} +GOLANGCI_LINT_URL := https://github.com/golangci/golangci-lint/releases/download/v${GOLANGCI_LINT_VERSION}/${GOLANGCI_LINT_ARCHIVE_NAME}.tar.gz + +export PATH := $(PWD)/bin:$(PATH) +export GO111MODULE=on + +default: lint test + +bin/${GOLANGCI_LINT_ARCHIVE_NAME}/: + mkdir -p bin + curl -L ${GOLANGCI_LINT_URL} | tar --directory bin/ --gzip --extract --verbose + +bin/golangci-lint: bin/${GOLANGCI_LINT_ARCHIVE_NAME}/ + ln -f -s $(PWD)/bin/${GOLANGCI_LINT_ARCHIVE_NAME}/golangci-lint $@ + touch $@ + +lint: bin/golangci-lint + golangci-lint run ./... + +test: + go test ./... \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a51ed8e --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# TODO +REWRITE THIS +only proxyprotocol.proxyprotocol.NewListener(rawListener) works, the sourcechecker and the logger are not there anymore or the choice to define the proxy protocol version + +# go-proxyprotocol + +[![GoDoc](https://godoc.org/github.com/c0va23/go-proxyprotocol?status.svg)](https://godoc.org/github.com/c0va23/go-proxyprotocol) +[![Go Report Card](https://goreportcard.com/badge/github.com/c0va23/go-proxyprotocol)](https://goreportcard.com/report/github.com/c0va23/go-proxyprotocol) +[![Build Status](https://travis-ci.org/c0va23/go-proxyprotocol.svg?branch=master)](https://travis-ci.org/c0va23/go-proxyprotocol) + +Golang package `github.com/c0va23/go-proxyprotocol' provide receiver for +[HA ProxyProtocol v1 and v2](http://www.haproxy.org/download/2.0/doc/proxy-protocol.txt). + +This package provides a wrapper for the interface net.Listener, which extracts +remote and local address of the connection from the headers in the format +HA proxyprotocol. + +## Usage example + +```go +package main + +import ( + "fmt" + "log" + "net" + "net/http" + + "github.com/c0va23/go-proxyprotocol" +) + +func main() { + rawList, _ := net.Listen("tcp", ":8080") + + list := proxyprotocol. + NewDefaultListener(rawList). + WithLogger(proxyprotocol.LoggerFunc(log.Printf)) + + http.Serve(list, http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + log.Printf("Remote Addr: %s, URI: %s", req.RemoteAddr, req.RequestURI) + fmt.Fprintf(res, "Hello, %s!\n", req.RemoteAddr) + })) +} +``` + +DefaultListener try parse proxyprotocol v1 and v2 header. If header signature +not recognized, then used raw connection. + +If you want to use only proxy protocol V1 or v2 headers, you can initialize the +listener as follows: + +```go +list := proxyprotocol.NewListener(rawList, proxyprotocol.TextHeaderParserBuilder) +``` + +## Implementation status + +### Human-readable header format (Version 1) +- [x] UNKNOWN +- [x] IPv4 +- [x] IPv6 + +### Binary header format (version 2) +- [x] Unspec +- [x] TCP over IPv4 +- [x] TCP over IPv6 +- [ ] UDP over IPv4 +- [ ] UDP over IPv6 +- [ ] Unix Stream +- [ ] Unix Datagram diff --git a/binary.go b/binary.go new file mode 100644 index 0000000..da0b68a --- /dev/null +++ b/binary.go @@ -0,0 +1,58 @@ +package proxyprotocol + +// BinarySignature is magic prefix for proxyprotocol Binary +var ( + BinarySignature = []byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A} + BinarySignatureLen = len(BinarySignature) +) + +// BinaryVersion2 bits +const ( + BinaryVersion2 byte = 0x20 + BinaryVersionMask byte = 0xF0 +) + +// Commands +const ( + BinaryCommandLocal byte = 0x00 + BinaryCommandProxy byte = 0x01 + BinaryCommandMask byte = 0x0F +) + +// Address families +const ( + BinaryAFUnspec byte = 0x00 + BinaryAFInet byte = 0x10 + BinaryAFInet6 byte = 0x20 + BinaryAFUnix byte = 0x30 + BinaryAFMask byte = 0xF0 +) + +// Transport protocols +const ( + BinaryTPUnspec byte = 0x00 + BinaryTPStream byte = 0x01 + BinaryTPDgram byte = 0x02 + BinaryTPMask byte = 0x0F +) + +// Protocol variants +var ( + BinaryProtocolUnspec = BinaryAFUnspec | BinaryTPUnspec + BinaryProtocolTCPoverIPv4 = BinaryAFInet | BinaryTPStream + BinaryProtocolUDPoverIPv4 = BinaryAFInet | BinaryTPDgram + BinaryProtocolTCPoverIPv6 = BinaryAFInet6 | BinaryTPStream + BinaryProtocolUDPoverIPv6 = BinaryAFInet6 | BinaryTPDgram + BinaryProtocolUnixStream = BinaryAFUnix | BinaryTPStream + BinaryProtocolUnixDatagram = BinaryAFUnix | BinaryTPDgram +) + +// Expected address length +var ( + BinaryPortLen = 2 +) + +// TLV types +const ( + TLVTypeNoop byte = 0x04 +) diff --git a/binary_receive.go b/binary_receive.go new file mode 100644 index 0000000..b52d880 --- /dev/null +++ b/binary_receive.go @@ -0,0 +1,132 @@ +package proxyprotocol + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "net" +) + +// Errors +var ( + ErrUnknownVersion = errors.New("unknown version") + ErrUnknownCommand = errors.New("unknown command") + ErrUnexpectedAddressLen = errors.New("unexpected address length") +) + +// Meta buffer byte position +const ( + versionCommandPos = 0 + protocolPos = 1 + addressLenStartPos = 2 + addressLenEndPos = 4 +) + +// BinaryHeaderParser parse proxyprotocol header from Reader +type BinaryHeaderParser struct { + logger Logger +} + +// NewBinaryHeaderParser construct BinaryHeaderParser +func NewBinaryHeaderParser(logger Logger) BinaryHeaderParser { + return BinaryHeaderParser{ + logger: logger, + } +} + +// Parse buffer +func (parser BinaryHeaderParser) Parse(buf *bufio.Reader) (*Header, error) { + magicBuf, err := buf.Peek(BinarySignatureLen) + if err != nil { + parser.logger.Printf("Read magic prefix error: %s", err) + return nil, err + } + + if !bytes.Equal(magicBuf, BinarySignature) { + return nil, ErrInvalidSignature + } + + _, err = buf.Discard(BinarySignatureLen) + if err != nil { + return nil, err + } + + metaBuf := make([]byte, addressLenEndPos) + if _, err = buf.Read(metaBuf); err != nil { + parser.logger.Printf("Read meta error: %s", err) + return nil, err + } + + versionCommandByte := metaBuf[versionCommandPos] + + if versionCommandByte&BinaryVersionMask != BinaryVersion2 { + return nil, ErrUnknownVersion + } + + addressSizeBuf := metaBuf[addressLenStartPos:addressLenEndPos] + addressesLen := int(binary.BigEndian.Uint16(addressSizeBuf)) + parser.logger.Printf("Addresses len: %d", addressesLen) + + addressesBuf := make([]byte, addressesLen) + addressReaded, err := buf.Read(addressesBuf) + if err != nil { + parser.logger.Printf("Read address error: %s", err) + return nil, err + } + parser.logger.Printf("Address readed: %d", addressReaded) + + switch versionCommandByte & BinaryCommandMask { + case BinaryCommandProxy: + return parserBinaryCommandHeader(metaBuf[protocolPos], addressesBuf) + case BinaryCommandLocal: + return nil, nil + default: + return nil, ErrUnknownCommand + } +} + +func parserBinaryCommandHeader(protocol byte, addressesBuf []byte) (*Header, error) { + switch protocol & BinaryAFMask { + case BinaryProtocolUnspec: + return nil, nil + case BinaryAFInet: + return parseAddressData(addressesBuf, net.IPv4len) + case BinaryAFInet6: + return parseAddressData(addressesBuf, net.IPv6len) + default: + return nil, ErrUnknownProtocol + } +} + +func parseAddressData(addressesBuf []byte, ipLen int) (*Header, error) { + expectedBufSize := 2 * (ipLen + BinaryPortLen) + if len(addressesBuf) < expectedBufSize { + return nil, ErrUnexpectedAddressLen + } + + srcIP := make(net.IP, ipLen) + copy(srcIP, addressesBuf[:ipLen]) + addressesBuf = addressesBuf[ipLen:] + + dstIP := make(net.IP, ipLen) + copy(dstIP, addressesBuf[:ipLen]) + addressesBuf = addressesBuf[ipLen:] + + srcPort := binary.BigEndian.Uint16(addressesBuf[:BinaryPortLen]) + addressesBuf = addressesBuf[BinaryPortLen:] + + dstPort := binary.BigEndian.Uint16(addressesBuf[:BinaryPortLen]) + // addressesBuf = addressesBuf[BinaryPortLen:] + + return &Header{ + SrcAddr: &net.TCPAddr{ + IP: srcIP, + Port: int(srcPort), + }, + DstAddr: &net.TCPAddr{ + IP: dstIP, + Port: int(dstPort), + }, + }, nil +} diff --git a/conn.go b/conn.go new file mode 100644 index 0000000..1c4f3f0 --- /dev/null +++ b/conn.go @@ -0,0 +1,86 @@ +package proxyprotocol + +import ( + "bufio" + "net" + "sync" +) + +// Conn is wrapper on net.Conn with RemoteAddr() override. +// +// On first call Read() or RemoteAddr() parse proxyprotocol header and store +// local and remote addresses. +type Conn struct { + net.Conn + logger Logger + readBuf *bufio.Reader + header *Header + headerErr error + headerParser HeaderParser + trustedAddr bool + once sync.Once +} + +// NewConn create wrapper on net.Conn. +func NewConn(conn net.Conn, headerParser HeaderParser) net.Conn { + readBuf := bufio.NewReaderSize(conn, bufferSize) + + return &Conn{ + Conn: conn, + readBuf: readBuf, + logger: nil, + headerParser: headerParser, + trustedAddr: true, + } +} + +func (conn *Conn) parseHeader() { + conn.header, conn.headerErr = conn.headerParser.Parse(conn.readBuf) + if conn.headerErr != nil { + conn.logger.Printf("Header parse error: %s", conn.headerErr) + return + } + conn.logger.Printf("Header parsed %v", conn.header) +} + +// Read on first call parse proxyprotocol header. +// +// If header parser return error, then error stored and returned. Otherwise call +// Read on source connection. +// +// Following calls of Read function check parse header error. +// If error not nil, then error returned. Otherwise called source "conn.Read". +func (conn *Conn) Read(buf []byte) (int, error) { + conn.once.Do(conn.parseHeader) + + if conn.headerErr != nil { + return 0, conn.headerErr + } + + return conn.readBuf.Read(buf) +} + +// LocalAddr proxy to conn.LocalAddr +func (conn *Conn) LocalAddr() net.Addr { + conn.once.Do(conn.parseHeader) + + if conn.trustedAddr && conn.header != nil { + return conn.header.DstAddr + } + + return conn.Conn.LocalAddr() +} + +// RemoteAddr on first call parse proxyprotocol header. +// +// If header parser return header, then return source address from header. +// Otherwise return original source address. +func (conn *Conn) RemoteAddr() net.Addr { + conn.once.Do(conn.parseHeader) + + if conn.trustedAddr && conn.header != nil { + return conn.header.SrcAddr + } + + return conn.Conn.RemoteAddr() +} diff --git a/fallback_receive.go b/fallback_receive.go new file mode 100644 index 0000000..0689642 --- /dev/null +++ b/fallback_receive.go @@ -0,0 +1,84 @@ +package proxyprotocol + +import ( + "bufio" + "errors" +) + +// StubHeaderParser always return nil Header +type StubHeaderParser struct{} + +// NewStubHeaderParser construct StubHeaderParser +func NewStubHeaderParser() StubHeaderParser { + return StubHeaderParser{} +} + +// Parse always return nil, nil +func (parser StubHeaderParser) Parse(*bufio.Reader) (*Header, error) { + return nil, nil +} + +// FallbackHeaderParserBuilder build FallbackHeaderParser +type FallbackHeaderParserBuilder []HeaderParserBuilder + +// NewFallbackHeaderParserBuilder construct FallbackHeaderParserBuilder +func NewFallbackHeaderParserBuilder( + headerParserBuilders ...HeaderParserBuilder, +) FallbackHeaderParserBuilder { + return FallbackHeaderParserBuilder(headerParserBuilders) +} + +// Build FallbackHeaderParser from headerParserBuilders +func (headerParserBuilders FallbackHeaderParserBuilder) Build(logger Logger) HeaderParser { + headerParsers := make([]HeaderParser, 0, len(headerParserBuilders)) + for _, headerParserBuilder := range headerParserBuilders { + headerParser := headerParserBuilder.Build(logger) + headerParsers = append(headerParsers, headerParser) + } + return FallbackHeaderParser{ + Logger: logger, + HeaderParsers: headerParsers, + } +} + +// ErrInvalidHeader returned by FallbackHeaderParser when all headerParsers return +// ErrInvalidSignature +var ErrInvalidHeader = errors.New("invalid header") + +// FallbackHeaderParser iterate over HeaderParser until parser not return nil error. +type FallbackHeaderParser struct { + Logger Logger + HeaderParsers []HeaderParser +} + +// NewFallbackHeaderParser create new instance of FallbackHeaderParser +func NewFallbackHeaderParser(logger Logger, headerParsers ...HeaderParser) FallbackHeaderParser { + return FallbackHeaderParser{ + Logger: logger, + HeaderParsers: headerParsers, + } +} + +// Parse iterate over headerParsers call Parse(). +// +// If any parser return not nil or not ErrInvalidSignature error, then return its error. +// +// If any parser return nil error, then return header. +// +// If all parsers return error ErrInvalidSignature, then return ErrInvalidHeader. +func (parser FallbackHeaderParser) Parse(buf *bufio.Reader) (*Header, error) { + for _, headerParser := range parser.HeaderParsers { + header, err := headerParser.Parse(buf) + switch err { + case nil: + parser.Logger.Printf("Use header remote addr") + return header, nil + case ErrInvalidSignature: + continue + default: + parser.Logger.Printf("Parse header error: %s", err) + return nil, err + } + } + return nil, ErrInvalidHeader +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e08aaed --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/c0va23/go-proxyprotocol + +go 1.12 + +require github.com/golang/mock v1.2.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..61e814a --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= diff --git a/listener.go b/listener.go new file mode 100644 index 0000000..d541b1e --- /dev/null +++ b/listener.go @@ -0,0 +1,78 @@ +package proxyprotocol + +import ( + "net" +) + +const bufferSize = 1400 + +// SourceChecker check trusted address +type SourceChecker func(net.Addr) (bool, error) + +// NewListener construct Listener +func NewListener(listener net.Listener) Listener { + return Listener{ + Listener: listener, + } +} + +// Listener implement net.Listener +type Listener struct { + net.Listener +} + +// HeaderParserBuilderFunc wrap builder func into HeaderParserBuilder +type HeaderParserBuilderFunc func(logger Logger) HeaderParser + +// Build implement HeaderParserBuilder for build func +func (funcBuilder HeaderParserBuilderFunc) Build(logger Logger) HeaderParser { + return funcBuilder(logger) +} + +// TextHeaderParserBuilder build TextHeaderParser +var TextHeaderParserBuilder = HeaderParserBuilderFunc(func(logger Logger) HeaderParser { + return NewTextHeaderParser(logger) +}) + +// BinaryHeaderParserBuilder build BinaryHeaderParser +var BinaryHeaderParserBuilder = HeaderParserBuilderFunc(func(logger Logger) HeaderParser { + return NewBinaryHeaderParser(logger) +}) + +// StubHeaderParserBuilder build StubHeaderParser +var StubHeaderParserBuilder = HeaderParserBuilderFunc(func(logger Logger) HeaderParser { + return NewStubHeaderParser() +}) + +// Otherwise connection wrapped into Conn with header parser. +func (listener Listener) Accept() (net.Conn, error) { + rawConn, err := listener.Listener.Accept() + if err != nil { + return nil, err + } + + logger := FallbackLogger{Logger: nil} + // trusted := true + // if listener.SourceChecker != nil { + // trusted, err = listener.SourceChecker(rawConn.RemoteAddr()) + // if err != nil { + // logger.Printf("Source check error: %s", err) + // return nil, err + // } + // } + + // if trusted { + // logger.Printf("Trusted connection") + // } else { + // logger.Printf("Not trusted connection") + // } + + // NOTE strictly a proxy protocol implementation without plain connections + headerParser := NewFallbackHeaderParserBuilder( + TextHeaderParserBuilder, + BinaryHeaderParserBuilder, + // StubHeaderParserBuilder, + ).Build(logger) + + return NewConn(rawConn, headerParser), nil +} diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..6b355ed --- /dev/null +++ b/logger.go @@ -0,0 +1,27 @@ +package proxyprotocol + +// Logger interface +type Logger interface { + Printf(format string, v ...interface{}) +} + +// LoggerFunc wrap Printf-like function into proxyprotocol.Logger +type LoggerFunc func(format string, v ...interface{}) + +// Printf call inner Printf-link function +func (logf LoggerFunc) Printf(format string, v ...interface{}) { + logf(format, v...) +} + +// FallbackLogger wrap Logger or nil +type FallbackLogger struct { + Logger +} + +// Printf call Printf on inner logger if it not nil +func (wrapper FallbackLogger) Printf(format string, v ...interface{}) { + if wrapper.Logger == nil { + return + } + wrapper.Logger.Printf(format, v...) +} diff --git a/proxyprotocol.go b/proxyprotocol.go new file mode 100644 index 0000000..00586df --- /dev/null +++ b/proxyprotocol.go @@ -0,0 +1,36 @@ +// Package proxyprotocol implement receiver for HA Proxy Protocol V1 and V2. +// +// Proxy Protocol spec http://www.haproxy.org/download/2.0/doc/proxy-protocol.txt +// +// This package provides a wrapper for the interface net.Listener, which extracts +// remote and local address of the connection from the headers in the format +// HA proxy protocol. +package proxyprotocol + +import ( + "bufio" + "errors" + "net" +) + +// Header struct represent header parsing result +type Header struct { + SrcAddr net.Addr + DstAddr net.Addr +} + +// HeaderParserBuilder build HeaderParser's +type HeaderParserBuilder interface { + Build(Logger) HeaderParser +} + +// HeaderParser describe interface for header parsers +type HeaderParser interface { + Parse(readBuf *bufio.Reader) (*Header, error) +} + +// Shared HeaderParser errors +var ( + ErrInvalidSignature = errors.New("invalid signature") + ErrUnknownProtocol = errors.New("unknown protocol") +) diff --git a/text.go b/text.go new file mode 100644 index 0000000..563c1f8 --- /dev/null +++ b/text.go @@ -0,0 +1,23 @@ +package proxyprotocol + +// TextSignature is prefix for proxyprotocol v1 +var ( + TextSignature = []byte("PROXY") + TextSeparator = " " + TextCR = byte('\r') + TextLF = byte('\n') + TextCRLF = []byte{TextCR, TextLF} +) + +var ( + textSignatureLen = len(TextSignature) + textAddressPartsLen = 4 + textPortBitSize = 16 +) + +// TextProtocol list +var ( + TextProtocolIPv4 = "TCP4" + TextProtocolIPv6 = "TCP6" + TextProtocolUnknown = "UNKNOWN" +) diff --git a/text_receive.go b/text_receive.go new file mode 100644 index 0000000..e8937fb --- /dev/null +++ b/text_receive.go @@ -0,0 +1,108 @@ +package proxyprotocol + +import ( + "bufio" + "bytes" + "errors" + "net" + "strconv" + "strings" +) + +// Text protocol errors +var ( + ErrInvalidAddressList = errors.New("invalid address list") + ErrInvalidIP = errors.New("invalid IP") + ErrInvalidPort = errors.New("invalid port") +) + +// TextHeaderParser for proxyprotocol v1 +type TextHeaderParser struct { + logger Logger +} + +// NewTextHeaderParser construct TextHeaderParser +func NewTextHeaderParser(logger Logger) TextHeaderParser { + return TextHeaderParser{ + logger: logger, + } +} + +// Parse proxyprotocol v1 header +func (parser TextHeaderParser) Parse(buf *bufio.Reader) (*Header, error) { + signatureBuf, err := buf.Peek(textSignatureLen) + if err != nil { + parser.logger.Printf("Read text signature error: %s", err) + return nil, err + } + + if !bytes.Equal(signatureBuf, TextSignature) { + return nil, ErrInvalidSignature + } + + headerLine, err := buf.ReadString(TextLF) + if err != nil { + parser.logger.Printf("Read header line error: %s", err) + return nil, err + } + + // Strip CR char on line end + if headerLine[len(headerLine)-2] == TextCR { + headerLine = headerLine[:len(headerLine)-2] + } + + headerParts := strings.Split(headerLine, TextSeparator) + + protocol := headerParts[1] + + switch protocol { + case TextProtocolUnknown: + return nil, nil + case TextProtocolIPv4, TextProtocolIPv6: + return parseTextHeader(headerParts) + default: + return nil, ErrUnknownProtocol + } +} + +func parseTextHeader(headerParts []string) (*Header, error) { + addressParts := headerParts[2:] + if textAddressPartsLen != len(addressParts) { + return nil, ErrInvalidAddressList + } + + srcIPStr := addressParts[0] + srcIP := net.ParseIP(srcIPStr) + if srcIP == nil { + return nil, ErrInvalidIP + } + + dstIPStr := addressParts[1] + dstIP := net.ParseIP(dstIPStr) + if dstIP == nil { + return nil, ErrInvalidIP + } + + srcPortSrt := addressParts[2] + srcPort, err := strconv.ParseUint(srcPortSrt, 10, textPortBitSize) + if err != nil { + return nil, ErrInvalidPort + } + + dstPortSrt := addressParts[3] + dstPort, err := strconv.ParseUint(dstPortSrt, 10, textPortBitSize) + if err != nil { + return nil, ErrInvalidPort + } + + return &Header{ + SrcAddr: &net.TCPAddr{ + IP: srcIP, + Port: int(srcPort), + }, + DstAddr: &net.TCPAddr{ + IP: dstIP, + Port: int(dstPort), + }, + }, nil +}