From 26b3adc77704e1ddb099d20cd9f7c644ecb44962 Mon Sep 17 00:00:00 2001 From: soypat Date: Sat, 28 Jun 2025 12:39:38 -0300 Subject: [PATCH] work on httpraw normalization and dns/dhcp bridge example --- dhcpv4/client.go | 12 ++++++- examples/bridge/main.go | 51 +++++++++++++++++++++++--- http/httpraw/header.go | 80 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 6 deletions(-) diff --git a/dhcpv4/client.go b/dhcpv4/client.go index 4c4c4be..3c730fe 100644 --- a/dhcpv4/client.go +++ b/dhcpv4/client.go @@ -267,6 +267,16 @@ func (c *Client) reset(xid uint32) { func (d *Client) State() ClientState { return d.state } +func (d *Client) BroadcastAddr() [4]byte { return d.broadcast } +func (d *Client) AssignedAddr() [4]byte { return d.offer } +func (d *Client) ServerAddr() [4]byte { return d.svip } +func (d *Client) RouterAddr() [4]byte { return d.router } +func (d *Client) GatewayAddr() [4]byte { return d.gateway } +func (d *Client) RebindingSeconds() uint32 { return d.tRebind } +func (d *Client) RenewalSeconds() uint32 { return d.tRenew } +func (d *Client) IPLeaseSeconds() uint32 { return d.tIPLease } +func (d *Client) AppendDNSServers(dst [][4]byte) [][4]byte { return append(dst, d.dns...) } + func (d *Client) CIDRBits() uint8 { if d.subnet == [4]byte{} { return 0 @@ -287,7 +297,7 @@ var defaultParamReqList = []byte{ } func maybeU32(b []byte) uint32 { - if len(b) < 4 { + if len(b) != 4 { return 0 } return binary.BigEndian.Uint32(b) diff --git a/examples/bridge/main.go b/examples/bridge/main.go index 9fd879e..e46f6ec 100644 --- a/examples/bridge/main.go +++ b/examples/bridge/main.go @@ -3,6 +3,7 @@ package main import ( "crypto/rand" "encoding/binary" + "errors" "flag" "fmt" "net" @@ -33,12 +34,19 @@ func main() { func run() (err error) { var ( - flagInterface = "tap0" - flagUseHTTP = false + flagInterface = "tap0" + flagUseHTTP = false + flagHostToResolve = "" ) flag.StringVar(&flagInterface, "i", flagInterface, "Interface to use. Either tap* or the name of an existing interface to bridge to.") flag.BoolVar(&flagUseHTTP, "http", flagUseHTTP, "Use HTTP tap interface.") + flag.StringVar(&flagHostToResolve, "host", flagHostToResolve, "Hostname to resolve via DNS.") flag.Parse() + _, err = dns.NewName(flagHostToResolve) + if err != nil { + flag.Usage() + return err + } var iface ltesto.Interface if flagUseHTTP { iface = ltesto.NewHTTPTapClient("http://127.0.0.1:7070") @@ -87,7 +95,26 @@ func run() (err error) { buf := make([]byte, mtu) var iframes []pcap.Frame lastAction := time.Now() + dnsOngoing := false for { + dhcpIsDone := stack.dhcp.State() == dhcpv4.StateBound + if dhcpIsDone { + if !dnsOngoing { + err = stack.StartLookupIP(flagHostToResolve) + if err != nil { + return err + } + dnsOngoing = true + } else { + addrs, err := stack.ResultLookupIP() + if err == nil { + // END PROGRAM. + fmt.Println(flagHostToResolve, "resolved to", addrs) + return nil + } + } + } + _ = dhcpIsDone clear(buf) nwrite, err := stack.Encapsulate(buf[:], 0) if err != nil { @@ -226,9 +253,23 @@ func (s *Stack) StartLookupIP(host string) error { } func (s *Stack) ResultLookupIP() ([]netip.Addr, error) { - s.dns.MessageCopyTo() - // s.dns.Answers() - return nil, nil + done, err := s.dns.MessageCopyTo(&s.lookup) + if err != nil { + return nil, err + } else if !done { + return nil, errors.New("DNS not done") + } + var addrs []netip.Addr + ans := s.lookup.Answers + for i := range ans { + data := ans[i].RawData() + if len(data) == 4 { + addrs = append(addrs, netip.AddrFrom4([4]byte(data))) + } else if len(data) == 16 { + addrs = append(addrs, netip.AddrFrom16([16]byte(data))) + } + } + return addrs, nil } func (s *Stack) BeginDHCPRequest() error { diff --git a/http/httpraw/header.go b/http/httpraw/header.go index 74d072c..abbc21f 100644 --- a/http/httpraw/header.go +++ b/http/httpraw/header.go @@ -1,6 +1,7 @@ package httpraw import ( + "bytes" "errors" "io" "net/http" @@ -404,3 +405,82 @@ type noCopy struct{} func (*noCopy) Lock() {} func (*noCopy) Unlock() {} + +// NormalizeKey normalizes a HTTP header key in-place. Returns true if buffer modified. +// Examples of normalization: +// - CONTENT -> Content +// - content-length -> Content-Length +// - cOnTeNt-LenGtH -> Content-Length +func NormalizeHeaderKey(b []byte) (modified bool) { + const asciiCapDiff = 'a' - 'A' + for i := -1; i < len(b); i++ { + ch := b[i] + nextToUpper := i == -1 || (ch == '-' && i < len(b)-1) + if nextToUpper { + i++ + isLower := b[i] >= 'a' && b[i] <= 'z' + if isLower { + modified = true + b[i] -= asciiCapDiff + } + } else { + isUpper := b[i] >= 'A' && b[i] <= 'Z' + if isUpper { + modified = true + b[i] += asciiCapDiff + } + } + } + return modified +} + +// CopyNormalizedHeaderValue copies the header value in the value buffer to dst. +// The result may be shrunk. The target and source buffers can only alias if the +// destination buffer 0 address is equal to value's 0 address. +// Header value normalization implies the replacement of \r\n\t with a single space. +func CopyNormalizedHeaderValue(dst []byte, value []byte) (n int, modified bool) { + if len(dst) < len(value) { + panic("httpraw.CopyNormalizedHeaderValue: dst buffer shorter than length") + } + lineStart := false + write := 0 + read := 0 + for { + rmStart := bytes.IndexByte(value[read:], '\n') + if rmStart < 0 { + write += copy(dst[write:], value[read:]) + break + } + omit := 1 + rmStart += read + if rmStart > 0 && value[rmStart] == '\r' { + rmStart-- + omit++ + } + + if rmStart+1 < len(value) && value[rmStart+1] == '\t' { + omit++ + } + n := copy(dst[write:], value[:rmStart]) + read += omit + n + write += n + } + return write, modified + for read := 0; read < len(value); read++ { + c := value[read] + switch { + case c == '\r' || c == '\n': + lineStart = c == '\n' + continue + case lineStart && c == '\t': + c = ' ' + modified = true + default: + lineStart = false + } + dst[write] = c + write++ + } + modified = modified || n != len(value) + return write, modified +}