mirror of
https://github.com/soypat/lneto.git
synced 2026-08-08 17:03:40 +00:00
remove old examples and update readme
This commit is contained in:
+5
-1
@@ -21,11 +21,15 @@ vendor/
|
||||
|
||||
# example binaries.
|
||||
/tap
|
||||
/httptap
|
||||
/xcurl
|
||||
/xnet
|
||||
/httpclient
|
||||
/stack
|
||||
**__debug_bin*
|
||||
# `__debug_bin` Debug binary generated in VSCode when using the built-in debugger.
|
||||
*bin
|
||||
/xnet
|
||||
|
||||
/bridge
|
||||
# IDE
|
||||
.vscode/
|
||||
|
||||
@@ -22,6 +22,18 @@ Userspace networking primitives.
|
||||
- Extremely simple networking stack construction. Can be used to teach basics of networking
|
||||
- Only one networking interface fulfilled by all implementations. See [abstractions](#abstractions).
|
||||
|
||||
## `xcurl` example
|
||||
You may try lneto out on linux with the [xcurl example](./examples/xcurl/) which gets an HTTP page by doing all the low-level networking part using absolutely no standard library.
|
||||
|
||||
- DHCP client address lease
|
||||
- ARP address resolution
|
||||
- DNS address resolution of requested host
|
||||
- HTTP over TCP/IPv4/Ethernet connection using
|
||||
- NTP time check (optional)
|
||||
- Print packet captures using lneto's [internet/pcap](./internet/pcap) package
|
||||
|
||||
See Developing section below for more information.
|
||||
|
||||
## Why?(!)
|
||||
`lneto` was created to have networking on systems with a networking interface (wifi or ethernet cable) but no operating-system provided networking facilties.
|
||||
|
||||
@@ -82,34 +94,34 @@ go mod download github.com/soypat/lneto@latest
|
||||
|
||||
## Developing (linux)
|
||||
|
||||
- [`tap`](./examples/tap) (linux only, root privilidges required) Program opens a TAP interface and assigns an IP address to it and exposes the interface via a HTTP interface. This program is run with root privilidges to facilitate debugging of lneto since no root privilidges are required to interact with the HTTP interface exposed.
|
||||
- [`examples/httptap`](./examples/httptap) (linux only, root privilidges required) Program opens a TAP interface and assigns an IP address to it and exposes the interface via a HTTP interface. This program is run with root privilidges to facilitate debugging of lneto since no root privilidges are required to interact with the HTTP interface exposed.
|
||||
- `POST http://127.0.0.1:7070/send`: Receives a POST with request body containing JSON string of data to send over TAP interface. Response contains only status code.
|
||||
- `GET http://127.0.0.1:7070/recv`: Receives a GET request. Response contains a JSON string of oldest unread TAP interface packet. If string is empty then there is no more data to read.
|
||||
|
||||
- [`stack`](./examples/stack) Contains stack implementation which can interact with `tap` program. No root privilidges required.
|
||||
- Can expose a HTTP server.
|
||||
- [`xcurl`](./examples/xcurl) Contains example of a application that uses lneto and can attach to a linux tap/bridge interface or a [httptap](./examples/httptap)(with -ihttp flag) to work. When using httptap can be run as non-root user to be debugged comfortably.
|
||||
- Example: `go run ./examples/xcurl -host google.com -ihttp`
|
||||
|
||||
To run the HTTP TAP server run the following commands. Requires elevated privilidges!
|
||||
### Quick run xcurl
|
||||
Run xcurl over httptap interface. Requires running two programs in separate shell/consoles in linux:
|
||||
```sh
|
||||
# Build+Run HTTP Tap server from one shell, this will expose the `tap0` TAP interface over an HTTP interface at http://127.0.0.1:7070 on /recv and /send endpoints.
|
||||
go build ./examples/tap && sudo ./tap
|
||||
go build ./examples/httptap && sudo ./httpap
|
||||
```
|
||||
|
||||
Now run the application you wish to test without elevated privilidges. Stackbasic shows a basic HTTP demo in action.
|
||||
No privilidge escalation required for xcurl using `-ihttp` flag which taps using `httptap`:
|
||||
```sh
|
||||
go run ./examples/stackbasic
|
||||
go run ./examples/xcurl -host google.com -ihttp
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Wireshark and Packet Capture API
|
||||
Using the provided method of interfacing mean's you'll always be able to easily reach the TAP interface on your machine over HTTP from any process, be it Python or Go. To visualize the packets over the interface we suggest using **Wireshark** and selecting the `tap0` interface which will show all activity over the HTTP TAP interface created with [`./examples/tap`](./examples/tap/main.go).
|
||||
Using the provided method of interfacing mean's you'll always be able to easily reach the TAP interface on your machine over HTTP from any process, be it Python or Go. To visualize the packets over the interface we suggest using **Wireshark** and selecting the `tap0` interface which will show all activity over the HTTP TAP interface created with [`./examples/httptap`](./examples/httptap/main.go).
|
||||
|
||||
Alternatively there's the [`internet/pcap`](./internet/pcap) package that does the same thing as Wireshark but as a Go API. Here's the result of running xnet example with pcap logging:
|
||||
Alternatively there's the [`internet/pcap`](./internet/pcap) package that does the same thing as Wireshark but as a Go API. Here's the result of running xcurl example with pcap logging:
|
||||
|
||||
|
||||
```log
|
||||
go run ./examples/xnet -httpget -host google.com -ihttp -ntp
|
||||
go run ./examples/xcurl -host google.com -ihttp -ntp
|
||||
softrand 1767229198
|
||||
NIC hardware address: d8:5e:d3:43:03:eb bridgeHW: d8:5e:d3:43:03:eb mtu: 1500 addr: 192.168.1.53/24
|
||||
OUT 328 [Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=255.255.255.255 | UDP [RFC768] len=8; (Source port)=68; (Destination port)=67 | DHCPv4 len=285; op=1; Flags=0x0000; (Client Address)=us; (Offered Address)=us; (Server Next Address)=255.255.255.255; (Relay Agent Address)=us; (Client Hardware Address)=d85e:d343:3eb::]
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/soypat/lneto/arp"
|
||||
"github.com/soypat/lneto/dhcpv4"
|
||||
"github.com/soypat/lneto/dns"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/internal"
|
||||
"github.com/soypat/lneto/internal/ltesto"
|
||||
"github.com/soypat/lneto/internet"
|
||||
"github.com/soypat/lneto/internet/pcap"
|
||||
"github.com/soypat/lneto/ntp"
|
||||
)
|
||||
|
||||
var softRand = time.Now().Unix()
|
||||
|
||||
func main() {
|
||||
err := run()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("success")
|
||||
}
|
||||
|
||||
func run() (err error) {
|
||||
var (
|
||||
flagInterface = "tap0"
|
||||
flagUseHTTP = false
|
||||
flagHostToResolve = ""
|
||||
flagRequestedIP = ""
|
||||
flagDoNTP = false
|
||||
)
|
||||
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.StringVar(&flagRequestedIP, "addr", flagRequestedIP, "IP address to request via DHCP.")
|
||||
flag.BoolVar(&flagDoNTP, "ntp", flagDoNTP, "Do NTP round and print result time")
|
||||
flag.Parse()
|
||||
fmt.Println("softrand", softRand)
|
||||
_, 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")
|
||||
} else {
|
||||
if strings.HasPrefix(flagInterface, "tap") {
|
||||
tap, err := internal.NewTap(flagInterface, netip.MustParsePrefix("192.168.1.1/24"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iface = tap
|
||||
} else {
|
||||
bridge, err := internal.NewBridge(flagInterface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iface = bridge
|
||||
}
|
||||
}
|
||||
defer iface.Close()
|
||||
|
||||
nicHW, err := iface.HardwareAddress6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
brHW := nicHW
|
||||
mtu, err := iface.MTU()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nicAddr, err := iface.IPMask()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("NIC hardware address:", net.HardwareAddr(nicHW[:]).String(), "bridgeHW:", net.HardwareAddr(brHW[:]).String(), "mtu:", mtu, "addr:", nicAddr.String())
|
||||
var stack Stack
|
||||
|
||||
err = stack.Reset(brHW, netip.AddrFrom4([4]byte{}), uint16(mtu))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := make([]byte, mtu)
|
||||
lastAction := time.Now()
|
||||
const (
|
||||
stateDHCP = iota
|
||||
stateInitARP
|
||||
stateDNSNTP
|
||||
stateNTP
|
||||
stateDNS
|
||||
stateDone
|
||||
)
|
||||
|
||||
err = stack.BeginDHCPRequest([4]byte{192, 168, 1, 96})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state := stateDHCP
|
||||
prevState := state
|
||||
for {
|
||||
switch state {
|
||||
case stateDHCP:
|
||||
dhcpIsDone := stack.dhcp.State() == dhcpv4.StateBound
|
||||
if dhcpIsDone {
|
||||
state = stateInitARP
|
||||
assigned4, ok := stack.dhcp.AssignedAddr()
|
||||
if !ok {
|
||||
return errors.New("DHCP client address not assigned")
|
||||
}
|
||||
err = stack.ip.SetAddr(netip.AddrFrom4(assigned4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routeraddr, ok := stack.dhcp.RouterAddr()
|
||||
if !ok {
|
||||
return errors.New("DHCP router address not assigned")
|
||||
}
|
||||
err = stack.StartResolveHardwareAddress6(netip.AddrFrom4(routeraddr))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case stateInitARP:
|
||||
router, ok := stack.dhcp.RouterAddr()
|
||||
if !ok {
|
||||
return errors.New("DHCP router address not assigned")
|
||||
}
|
||||
hw, err := stack.ResultResolveHardwareAddress6(netip.AddrFrom4(router))
|
||||
if err == nil {
|
||||
stack.link.SetGateway6(hw)
|
||||
if flagDoNTP {
|
||||
state = stateDNSNTP
|
||||
err = stack.StartLookupIP("pool.ntp.org")
|
||||
} else {
|
||||
state = stateDNS
|
||||
err = stack.StartLookupIP(flagHostToResolve)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case stateDNSNTP:
|
||||
addrs, done, err := stack.ResultLookupIP()
|
||||
if err == nil {
|
||||
state = stateNTP
|
||||
fmt.Println("START NTP")
|
||||
err = stack.StartNTP(addrs[0])
|
||||
} else if !done {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case stateNTP:
|
||||
offset, done := stack.ResultNTP()
|
||||
if done {
|
||||
relative := "behind"
|
||||
if offset < 0 {
|
||||
relative = "ahead"
|
||||
}
|
||||
fmt.Println("NTP completed. You are", offset.Abs(), relative, "of the NTP server")
|
||||
state = stateDNS
|
||||
err = stack.StartLookupIP(flagHostToResolve)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case stateDNS:
|
||||
addrs, done, err := stack.ResultLookupIP()
|
||||
if err == nil {
|
||||
fmt.Println(flagHostToResolve, "resolved to", addrs)
|
||||
return nil
|
||||
} else if done {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if prevState != state {
|
||||
fmt.Println("STATE CHANGE", prevState, state)
|
||||
}
|
||||
prevState = state
|
||||
|
||||
clear(buf)
|
||||
nwrite, err := stack.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
fmt.Println("ERR:ENCAPSULATE", err)
|
||||
} else if nwrite > 0 {
|
||||
n, err := iface.Write(buf[:nwrite])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if n != nwrite {
|
||||
return fmt.Errorf("mismatch written bytes %d!=%d", nwrite, n)
|
||||
}
|
||||
}
|
||||
|
||||
clear(buf)
|
||||
nread, err := iface.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if nread > 0 {
|
||||
err = stack.Demux(buf[:nread], 0)
|
||||
if err != nil {
|
||||
fmt.Println("ERR:DEMUX", err)
|
||||
}
|
||||
}
|
||||
|
||||
if nread == 0 && nwrite == 0 && time.Since(lastAction) > 4*time.Second {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
} else {
|
||||
lastAction = time.Now()
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Stack struct {
|
||||
link internet.StackEthernet
|
||||
ip internet.StackIP
|
||||
arp arp.Handler
|
||||
udps internet.StackPorts
|
||||
dhcp dhcpv4.Client
|
||||
dns dns.Client
|
||||
ednsopt dns.Resource
|
||||
lookup dns.Message
|
||||
ntp ntp.Client
|
||||
sysprec int8 // NTP system precision.
|
||||
|
||||
// Packet capture and top level filtering.
|
||||
shark pcap.PacketBreakdown
|
||||
aux []pcap.Frame
|
||||
}
|
||||
|
||||
func (s *Stack) Demux(b []byte, _ int) (err error) {
|
||||
s.aux, err = s.shark.CaptureEthernet(s.aux[:0], b, 0)
|
||||
topFrame := s.aux[len(s.aux)-1]
|
||||
isOK := topFrame.Protocol == "DHCPv4" || // Allow DHCP, DNS and NTP responses.
|
||||
topFrame.Protocol == "DNS" ||
|
||||
topFrame.Protocol == "NTP" ||
|
||||
topFrame.Protocol == ethernet.TypeARP // Allow ARP responses.
|
||||
if !isOK {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("IN", s.aux, err.Error())
|
||||
} else {
|
||||
fmt.Println("IN", s.aux)
|
||||
}
|
||||
return s.link.Demux(b, 0)
|
||||
}
|
||||
|
||||
func (s *Stack) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (int, error) {
|
||||
n, err := s.link.Encapsulate(carrierData, offsetToIP, offsetToFrame)
|
||||
if n > 0 {
|
||||
iframes, errpcap := s.shark.CaptureEthernet(s.aux[:0], carrierData[:n], 0)
|
||||
if errpcap != nil {
|
||||
fmt.Println("OU", iframes, errpcap.Error())
|
||||
} else {
|
||||
fmt.Println("OU", iframes)
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s *Stack) Reset(mac [6]byte, addr netip.Addr, mtu uint16) error {
|
||||
const maxNodes = 8
|
||||
err := s.link.Reset6(mac, ethernet.BroadcastAddr(), int(mtu), maxNodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.ip.Reset(addr, maxNodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipaddr := addr.AsSlice()
|
||||
proto := ethernet.TypeIPv4
|
||||
if addr.Is6() {
|
||||
proto = ethernet.TypeIPv6
|
||||
}
|
||||
err = s.arp.Reset(arp.HandlerConfig{
|
||||
HardwareAddr: mac[:],
|
||||
ProtocolAddr: ipaddr,
|
||||
MaxQueries: 3,
|
||||
MaxPending: 3,
|
||||
HardwareType: 1,
|
||||
ProtocolType: proto,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.udps.ResetUDP(maxNodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now setup stacks.
|
||||
err = s.link.Register(&s.arp) // ARP.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.link.Register(&s.ip) // IPv4 | IPv6
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.ip.Register(&s.udps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.ip.SetLogger(slog.Default())
|
||||
var timebuf [32]time.Time
|
||||
s.sysprec = ntp.CalculateSystemPrecision(time.Now, timebuf[:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stack) StartLookupIP(host string) error {
|
||||
dnsSrvs := s.dhcp.DNSServerFirst()
|
||||
if !dnsSrvs.IsValid() {
|
||||
return errors.New("no valid DNS server")
|
||||
}
|
||||
name, err := dns.NewName(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.ednsopt.SetEDNS0(uint16(s.link.MTU())-100, 0, 0, nil)
|
||||
err = s.dns.StartResolve(uint16(softRand>>1)+1024, uint16(softRand), dns.ResolveConfig{
|
||||
Questions: []dns.Question{
|
||||
{
|
||||
Name: name,
|
||||
Type: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Additional: []dns.Resource{
|
||||
s.ednsopt,
|
||||
},
|
||||
EnableRecursion: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var u internet.StackUDPPort
|
||||
dns4 := dnsSrvs.As4()
|
||||
u.SetStackNode(&s.dns, dns4[:], dns.ServerPort)
|
||||
err = s.udps.Register(&u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("START LOOKUP", host, dns4[:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stack) ResultLookupIP() ([]netip.Addr, bool, error) {
|
||||
done, err := s.dns.MessageCopyTo(&s.lookup)
|
||||
if err != nil {
|
||||
return nil, done, err
|
||||
} else if !done {
|
||||
return nil, done, 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, done, nil
|
||||
}
|
||||
|
||||
func (s *Stack) ResultNTP() (time.Duration, bool) {
|
||||
return s.ntp.Offset(), s.ntp.IsDone()
|
||||
}
|
||||
|
||||
func (s *Stack) BeginDHCPRequest(request [4]byte) error {
|
||||
var buf [4]byte
|
||||
rand.Read(buf[:])
|
||||
xid := binary.LittleEndian.Uint32(buf[:])
|
||||
err := s.dhcp.BeginRequest(xid, dhcpv4.RequestConfig{
|
||||
RequestedAddr: request,
|
||||
ClientHardwareAddr: s.link.HardwareAddr6(),
|
||||
Hostname: "lneto",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var u internet.StackUDPPort
|
||||
u.SetStackNode(&s.dhcp, nil, dhcpv4.DefaultServerPort)
|
||||
err = s.udps.Register(&u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Stack) StartNTP(addr netip.Addr) error {
|
||||
s.ntp.Reset(s.sysprec, time.Now)
|
||||
var u internet.StackUDPPort
|
||||
addr4 := addr.As4()
|
||||
u.SetStackNode(&s.ntp, addr4[:], ntp.ServerPort)
|
||||
err := s.udps.Register(&u)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Stack) StartResolveHardwareAddress6(ip netip.Addr) error {
|
||||
if !ip.Is4() {
|
||||
return errors.New("unsupported or invalid IP address")
|
||||
}
|
||||
addr := ip.As4()
|
||||
return s.arp.StartQuery(nil, addr[:])
|
||||
}
|
||||
|
||||
func (s *Stack) ResultResolveHardwareAddress6(ip netip.Addr) (hw [6]byte, err error) {
|
||||
if !ip.Is4() {
|
||||
return hw, errors.New("unsupported or invalid IP address")
|
||||
}
|
||||
addr := ip.As4()
|
||||
hwslice, err := s.arp.QueryResult(addr[:])
|
||||
if err != nil {
|
||||
return hw, err
|
||||
} else if len(hwslice) != 6 {
|
||||
panic("unreachable slice hw length")
|
||||
}
|
||||
return [6]byte(hwslice), nil
|
||||
}
|
||||
|
||||
func clear(buf []byte) {
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func getField(frame pcap.Frame, pkt []byte, class pcap.FieldClass) uint64 {
|
||||
idx, err := frame.FieldByClass(class)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
v, _ := frame.FieldAsUint(idx, pkt)
|
||||
return v
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
"github.com/soypat/lneto/arp"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/http/httpraw"
|
||||
"github.com/soypat/lneto/internal/ltesto"
|
||||
"github.com/soypat/lneto/internet"
|
||||
"github.com/soypat/lneto/internet/pcap"
|
||||
"github.com/soypat/lneto/tcp"
|
||||
)
|
||||
|
||||
const (
|
||||
stackIP = "192.168.10.2"
|
||||
stackPort = 80
|
||||
iss = 100
|
||||
)
|
||||
|
||||
var stackHWAddr = [6]byte{0xc0, 0xff, 0xee, 0x00, 0xde, 0xad}
|
||||
|
||||
func main() {
|
||||
ip := netip.MustParseAddr(stackIP)
|
||||
tap := ltesto.NewHTTPTapClient("http://127.0.0.1:7070")
|
||||
|
||||
ippfx, _ := tap.IPMask()
|
||||
if !ippfx.Contains(ip) {
|
||||
log.Fatal("interface does not contain stack address")
|
||||
}
|
||||
addrPort := netip.AddrPortFrom(ip, stackPort)
|
||||
lg := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: slog.LevelDebug,
|
||||
}))
|
||||
|
||||
gatewayMAC, _ := tap.HardwareAddress6()
|
||||
mtu, _ := tap.MTU()
|
||||
|
||||
var stack Stack
|
||||
err := stack.Reset(stackHWAddr, gatewayMAC, addrPort.Addr(), mtu)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
listener, err := stack.OpenTCPListener(addrPort.Port())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer tap.Close()
|
||||
tap.ReadDiscard() // Discard all unread content.
|
||||
fmt.Println("hosting server at ", addrPort.String(), "over tap interface of mtu:", mtu, "prefix:", ippfx, "gateway:", net.HardwareAddr(gatewayMAC[:]).String())
|
||||
buf := make([]byte, mtu)
|
||||
var hdr httpraw.Header
|
||||
hdr.Reset(make([]byte, 0, 1024))
|
||||
const standbyDuration = 5 * time.Second
|
||||
lastHit := time.Now().Add(-standbyDuration)
|
||||
var cap pcap.PacketBreakdown
|
||||
var conn *tcp.Conn
|
||||
accepted := 0
|
||||
for {
|
||||
nread, err := tap.Read(buf[:])
|
||||
if err != nil {
|
||||
lg.Error("tap-err", slog.String("err", err.Error()))
|
||||
log.Fatal(err)
|
||||
} else if nread > 0 {
|
||||
frames, err := cap.CaptureEthernet(nil, buf[:nread], 0)
|
||||
if err == nil {
|
||||
flags := getTCPFlags(frames, buf[:nread])
|
||||
if flags == 0 {
|
||||
fmt.Println("IN", time.Now().Format("15:04:05.000"), frames)
|
||||
} else {
|
||||
fmt.Println("IN", time.Now().Format("15:04:05.000"), frames, flags.String())
|
||||
}
|
||||
}
|
||||
err = stack.ethernet.Demux(buf[:nread], 0)
|
||||
if err != nil {
|
||||
lg.Error("recv", slog.String("err", err.Error()), slog.Int("plen", nread))
|
||||
}
|
||||
}
|
||||
if conn == nil && listener.NumberOfReadyToAccept() > 0 {
|
||||
conn, err = listener.TryAccept()
|
||||
if err != nil {
|
||||
lg.Error("tryaccept", slog.String("err", err.Error()))
|
||||
}
|
||||
accepted++
|
||||
hdr.Reset(nil)
|
||||
lg.Info("ACCEPT!")
|
||||
}
|
||||
if conn != nil {
|
||||
done, err := doHTTP(conn, &hdr)
|
||||
if done {
|
||||
lg.Info("close forever")
|
||||
conn.Close()
|
||||
conn = nil
|
||||
}
|
||||
if err != nil {
|
||||
lg.Error("doHTTP", slog.String("err", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
nw, err := stack.ethernet.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
lg.Error("handle", slog.String("err", err.Error()))
|
||||
} else if nw > 0 {
|
||||
frames, err := cap.CaptureEthernet(nil, buf[:nread], 0)
|
||||
if err == nil {
|
||||
flags := getTCPFlags(frames, buf[:nread])
|
||||
if flags == 0 {
|
||||
fmt.Println("OU", time.Now().Format("15:04:05.000"), frames)
|
||||
} else {
|
||||
fmt.Println("OU", time.Now().Format("15:04:05.000"), frames, flags.String())
|
||||
}
|
||||
}
|
||||
_, err = tap.Write(buf[:nw])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
hit := nread > 0 || nw > 0
|
||||
if hit {
|
||||
// slogger.info("exchange", slog.Int("read", nread), slog.Int("nwrite", nw))
|
||||
lastHit = time.Now()
|
||||
} else {
|
||||
if time.Since(lastHit) > standbyDuration {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
} else {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doHTTP(conn *tcp.Conn, hdr *httpraw.Header) (done bool, err error) {
|
||||
const asRequest = false
|
||||
if conn.State() != tcp.StateEstablished || conn.BufferedInput() == 0 {
|
||||
return false, nil // No data yet.
|
||||
}
|
||||
fmt.Println("state is established; check request and send response")
|
||||
_, err = hdr.ReadFromLimited(conn, hdr.BufferFree())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
needMore, err := hdr.TryParse(asRequest)
|
||||
if needMore {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return true, err
|
||||
}
|
||||
// HTTP parsed succesfully!
|
||||
fmt.Println("GOT HTTP:\n", hdr.String())
|
||||
fmt.Println("sending response...")
|
||||
hdr.Reset(nil)
|
||||
hdr.SetStatus("200", "OK")
|
||||
data := `{"ok":true}`
|
||||
response, err := hdr.AppendResponse(nil)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
response = append(response, data...)
|
||||
_, err = conn.Write(response)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type Stack struct {
|
||||
ethernet internet.StackEthernet
|
||||
ip internet.StackIP
|
||||
tcpports internet.StackPorts
|
||||
arp arp.Handler
|
||||
}
|
||||
|
||||
func (stack *Stack) Reset(ourMAC, gwMAC [6]byte, ip netip.Addr, mtu int) (err error) {
|
||||
const maxNodes = 8
|
||||
err = stack.ethernet.Reset6(ourMAC, gwMAC, mtu, maxNodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = stack.ip.Reset(ip, maxNodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stack.tcpports.ResetTCP(maxNodes)
|
||||
ipaddr := ip.As4()
|
||||
err = stack.arp.Reset(arp.HandlerConfig{
|
||||
HardwareAddr: ourMAC[:],
|
||||
ProtocolAddr: ipaddr[:],
|
||||
MaxQueries: 2,
|
||||
MaxPending: 2,
|
||||
HardwareType: 1,
|
||||
ProtocolType: ethernet.TypeIPv4,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Register stacks and nodes.
|
||||
err = stack.ethernet.Register(&stack.arp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = stack.ethernet.Register(&stack.ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = stack.ip.Register(&stack.tcpports)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (stack *Stack) Recv(b []byte) error {
|
||||
return stack.ethernet.Demux(b, 0)
|
||||
}
|
||||
|
||||
func (stack *Stack) Send(b []byte) (int, error) {
|
||||
return stack.ethernet.Encapsulate(b, -1, 0)
|
||||
}
|
||||
|
||||
func (stack *Stack) OpenTCPListener(port uint16) (*tcp.Listener, error) {
|
||||
var listener tcp.Listener
|
||||
err := listener.Reset(port, naiveTCPPool{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = stack.tcpports.Register(&listener) // Passive TCP requires no MAC setting.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &listener, nil
|
||||
}
|
||||
|
||||
func (stack *Stack) OpenPassiveTCP(port uint16, iss tcp.Value) (*tcp.Conn, error) {
|
||||
mtu := stack.ethernet.MTU()
|
||||
conn := new(tcp.Conn)
|
||||
err := conn.Configure(tcp.ConnConfig{
|
||||
RxBuf: make([]byte, mtu),
|
||||
TxBuf: make([]byte, mtu),
|
||||
TxPacketQueueSize: 3,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = conn.OpenListen(port, iss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = stack.tcpports.Register(conn) // Passive MAC with no listening.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
const tblhex = "0123456789abcdef"
|
||||
|
||||
func getTCPFlags(frames []pcap.Frame, pkt []byte) (flags tcp.Flags) {
|
||||
for i := range frames {
|
||||
if frames[i].Protocol != lneto.IPProtoTCP {
|
||||
continue
|
||||
}
|
||||
iflags, err := frames[i].FieldByClass(pcap.FieldClassFlags)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
v, err := frames[i].FieldAsUint(iflags, pkt)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return tcp.Flags(v)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type naiveTCPPool struct {
|
||||
}
|
||||
|
||||
func (naiveTCPPool) GetTCP() (*tcp.Conn, tcp.Value) {
|
||||
var buf [4]byte
|
||||
rand.Read(buf[:])
|
||||
randVal := binary.LittleEndian.Uint32(buf[:])
|
||||
var conn tcp.Conn
|
||||
err := conn.Configure(tcp.ConnConfig{
|
||||
RxBuf: make([]byte, 1024),
|
||||
TxBuf: make([]byte, 1024),
|
||||
TxPacketQueueSize: 3,
|
||||
Logger: slog.Default(),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &conn, tcp.Value(randVal)
|
||||
}
|
||||
|
||||
func (naiveTCPPool) PutTCP(*tcp.Conn) {}
|
||||
@@ -1,412 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
"github.com/soypat/lneto/arp"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/http/httpraw"
|
||||
"github.com/soypat/lneto/internal"
|
||||
"github.com/soypat/lneto/internal/ltesto"
|
||||
"github.com/soypat/lneto/internet"
|
||||
"github.com/soypat/lneto/internet/pcap"
|
||||
"github.com/soypat/lneto/ipv4"
|
||||
"github.com/soypat/lneto/tcp"
|
||||
)
|
||||
|
||||
const (
|
||||
stackIP = "192.168.10.2"
|
||||
stackPort = 80
|
||||
iss = 100
|
||||
)
|
||||
|
||||
var stackHWAddr = [6]byte{0xc0, 0xff, 0xee, 0x00, 0xde, 0xad}
|
||||
|
||||
func main() {
|
||||
ip := netip.MustParseAddr(stackIP)
|
||||
tap := ltesto.NewHTTPTapClient("http://127.0.0.1:7070")
|
||||
|
||||
ippfx, _ := tap.IPMask()
|
||||
if !ippfx.Contains(ip) {
|
||||
log.Fatal("interface does not contain stack address")
|
||||
}
|
||||
addrPort := netip.AddrPortFrom(ip, stackPort)
|
||||
lg := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: slog.LevelDebug,
|
||||
}))
|
||||
|
||||
slogger := logger{lg}
|
||||
gatewayMAC, _ := tap.HardwareAddress6()
|
||||
mtu, _ := tap.MTU()
|
||||
lStack, handler, err := NewEthernetTCPStack(stackHWAddr, gatewayMAC, addrPort, uint16(mtu), slogger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = handler.OpenListen(addrPort.Port(), iss)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer tap.Close()
|
||||
tap.ReadDiscard() // Discard all unread content.
|
||||
fmt.Println("hosting server at ", addrPort.String(), "over tap interface of mtu:", mtu, "prefix:", ippfx, "gateway:", net.HardwareAddr(gatewayMAC[:]).String())
|
||||
buf := make([]byte, mtu)
|
||||
var hdr httpraw.Header
|
||||
hdr.Reset(make([]byte, 0, 1024))
|
||||
const standbyDuration = 5 * time.Second
|
||||
lastHit := time.Now().Add(-standbyDuration)
|
||||
var cap pcap.PacketBreakdown
|
||||
for {
|
||||
nread, err := tap.Read(buf[:])
|
||||
if err != nil {
|
||||
slogger.error("tap-err", slog.String("err", err.Error()))
|
||||
log.Fatal(err)
|
||||
} else if nread > 0 {
|
||||
frames, err := cap.CaptureEthernet(nil, buf[:nread], 0)
|
||||
if err == nil {
|
||||
flags := getTCPFlags(frames, buf[:nread])
|
||||
if flags == 0 {
|
||||
fmt.Println("IN", time.Now().Format("15:04:05.000"), frames)
|
||||
} else {
|
||||
fmt.Println("IN", time.Now().Format("15:04:05.000"), frames, flags.String())
|
||||
}
|
||||
}
|
||||
err = lStack.RecvEth(buf[:nread])
|
||||
if err != nil {
|
||||
slogger.error("recv", slog.String("err", err.Error()), slog.Int("plen", nread))
|
||||
}
|
||||
}
|
||||
doHTTP(handler, &hdr)
|
||||
nw, err := lStack.HandleEth(buf[:])
|
||||
if err != nil {
|
||||
slogger.error("handle", slog.String("err", err.Error()))
|
||||
} else if nw > 0 {
|
||||
frames, err := cap.CaptureEthernet(nil, buf[:nread], 0)
|
||||
if err == nil {
|
||||
flags := getTCPFlags(frames, buf[:nread])
|
||||
if flags == 0 {
|
||||
fmt.Println("OU", time.Now().Format("15:04:05.000"), frames)
|
||||
} else {
|
||||
fmt.Println("OU", time.Now().Format("15:04:05.000"), frames, flags.String())
|
||||
}
|
||||
}
|
||||
_, err = tap.Write(buf[:nw])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
hit := nread > 0 || nw > 0
|
||||
if hit {
|
||||
// slogger.info("exchange", slog.Int("read", nread), slog.Int("nwrite", nw))
|
||||
lastHit = time.Now()
|
||||
} else {
|
||||
if time.Since(lastHit) > standbyDuration {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
} else {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doHTTP(conn *tcp.Conn, hdr *httpraw.Header) error {
|
||||
const asRequest = false
|
||||
if conn.State() != tcp.StateEstablished || conn.BufferedInput() == 0 {
|
||||
return nil // No data yet.
|
||||
}
|
||||
fmt.Println("state is established; check request and send response")
|
||||
_, err := hdr.ReadFromLimited(conn, hdr.BufferFree())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needMore, err := hdr.TryParse(asRequest)
|
||||
if err != nil {
|
||||
if !needMore {
|
||||
fmt.Println("IT's SO GOVER")
|
||||
conn.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
// HTTP parsed succesfully!
|
||||
fmt.Println("GOT HTTP:\n", hdr.String())
|
||||
fmt.Println("sending response...")
|
||||
hdr.Reset(nil)
|
||||
hdr.SetStatus("200", "OK")
|
||||
data := `{"ok":true}`
|
||||
response, err := hdr.AppendResponse(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
response = append(response, data...)
|
||||
_, err = conn.Write(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewEthernetTCPStack(ourMAC, gwMAC [6]byte, ip netip.AddrPort, mtu uint16, slogger logger) (*LinkStack, *tcp.Conn, error) {
|
||||
var err error
|
||||
lStack := LinkStack{
|
||||
logger: slogger,
|
||||
mac: ourMAC,
|
||||
mtu: mtu,
|
||||
gwmac: gwMAC,
|
||||
}
|
||||
|
||||
var ipStack internet.StackIP
|
||||
addr := ip.Addr()
|
||||
addr4 := addr.As4()
|
||||
_ = addr4
|
||||
ipStack.SetAddr(addr)
|
||||
lStack.Register(handler{
|
||||
raddr: nil, //addr4[:],
|
||||
recv: ipStack.Demux,
|
||||
handle: ipStack.Encapsulate,
|
||||
proto: ethernet.TypeIPv4,
|
||||
lport: 0,
|
||||
})
|
||||
var conn tcp.Conn
|
||||
err = conn.Configure(tcp.ConnConfig{
|
||||
RxBuf: make([]byte, mtu),
|
||||
TxBuf: make([]byte, mtu),
|
||||
TxPacketQueueSize: 3,
|
||||
Logger: slog.Default(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = conn.OpenListen(ip.Port(), 100)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = ipStack.Register(&conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
proto := ethernet.TypeIPv4
|
||||
if ip.Addr().Is6() {
|
||||
proto = ethernet.TypeIPv6
|
||||
}
|
||||
var narp arp.Handler
|
||||
err = narp.Reset(arp.HandlerConfig{
|
||||
HardwareAddr: ourMAC[:],
|
||||
ProtocolAddr: ip.Addr().AsSlice(),
|
||||
MaxQueries: 4,
|
||||
MaxPending: 4,
|
||||
HardwareType: 1,
|
||||
ProtocolType: proto,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
arpStack := ARPStack{
|
||||
handler: narp,
|
||||
}
|
||||
err = lStack.Register(handler{
|
||||
recv: arpStack.Recv,
|
||||
handle: arpStack.Handle,
|
||||
proto: ethernet.TypeARP,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &lStack, &conn, nil
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
raddr []byte
|
||||
recv func([]byte, int) error
|
||||
handle func([]byte, int, int) (int, error)
|
||||
proto ethernet.Type
|
||||
lport uint16
|
||||
}
|
||||
|
||||
type LinkStack struct {
|
||||
handlers []handler
|
||||
logger
|
||||
mac [6]byte
|
||||
gwmac [6]byte
|
||||
mtu uint16
|
||||
}
|
||||
|
||||
func (ls *LinkStack) Register(h handler) error {
|
||||
proto := h.proto
|
||||
for i := range ls.handlers {
|
||||
if proto == ls.handlers[i].proto {
|
||||
return errors.New("protocol already registered")
|
||||
}
|
||||
}
|
||||
ls.handlers = append(ls.handlers, h)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LinkStack) RecvEth(ethFrame []byte) (err error) {
|
||||
efrm, err := ethernet.NewFrame(ethFrame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
etype := efrm.EtherTypeOrSize()
|
||||
dstaddr := efrm.DestinationHardwareAddr()
|
||||
var vld lneto.Validator
|
||||
if !efrm.IsBroadcast() && ls.mac != *dstaddr {
|
||||
goto DROP
|
||||
}
|
||||
efrm.ValidateSize(&vld)
|
||||
if err := vld.ErrPop(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range ls.handlers {
|
||||
h := &ls.handlers[i]
|
||||
if h.proto == etype {
|
||||
return h.recv(efrm.Payload(), 0)
|
||||
}
|
||||
}
|
||||
DROP:
|
||||
ls.info("LinkStack:drop-packet", slog.String("dsthw", net.HardwareAddr(dstaddr[:]).String()), slog.String("ethertype", efrm.EtherTypeOrSize().String()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LinkStack) HandleEth(dst []byte) (n int, err error) {
|
||||
mtu := ls.mtu
|
||||
if len(dst) < int(mtu) {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
efrm, err := ethernet.NewFrame(dst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
copy(efrm.DestinationHardwareAddr()[:], ls.gwmac[:]) // default set the gateway.
|
||||
for i := range ls.handlers {
|
||||
h := &ls.handlers[i]
|
||||
n, err = h.handle(dst[:mtu], 14, 14)
|
||||
if err != nil {
|
||||
ls.error("handling", slog.String("proto", ethernet.Type(h.proto).String()), slog.String("err", err.Error()))
|
||||
continue
|
||||
}
|
||||
if n > 0 {
|
||||
// Found packet
|
||||
*efrm.SourceHardwareAddr() = ls.mac
|
||||
efrm.SetEtherType(ethernet.Type(h.proto))
|
||||
return n + 14, nil
|
||||
}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
type ARPStack struct {
|
||||
handler arp.Handler
|
||||
}
|
||||
|
||||
func (as *ARPStack) Protocol() uint32 { return uint32(ethernet.TypeARP) }
|
||||
|
||||
func (as *ARPStack) Recv(EtherFrame []byte, arpOff int) error {
|
||||
afrm, _ := arp.NewFrame(EtherFrame[arpOff:])
|
||||
slog.Info("recv", slog.String("in", afrm.String()))
|
||||
return as.handler.Demux(EtherFrame, arpOff)
|
||||
}
|
||||
|
||||
func (as *ARPStack) Handle(EtherFrame []byte, offsetToIP, arpOff int) (int, error) {
|
||||
n, err := as.handler.Encapsulate(EtherFrame, offsetToIP, arpOff)
|
||||
if err != nil || n == 0 {
|
||||
return 0, err
|
||||
}
|
||||
afrm, _ := arp.NewFrame(EtherFrame[arpOff:])
|
||||
hwaddr, _ := afrm.Target()
|
||||
efrm, _ := ethernet.NewFrame(EtherFrame)
|
||||
copy(efrm.DestinationHardwareAddr()[:], hwaddr)
|
||||
slog.Info("handle", slog.String("out", afrm.String()))
|
||||
return n, err
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func (l logger) error(msg string, attrs ...slog.Attr) {
|
||||
internal.LogAttrs(l.log, slog.LevelError, msg, attrs...)
|
||||
}
|
||||
func (l logger) info(msg string, attrs ...slog.Attr) {
|
||||
internal.LogAttrs(l.log, slog.LevelInfo, msg, attrs...)
|
||||
}
|
||||
func (l logger) warn(msg string, attrs ...slog.Attr) {
|
||||
internal.LogAttrs(l.log, slog.LevelWarn, msg, attrs...)
|
||||
}
|
||||
func (l logger) debug(msg string, attrs ...slog.Attr) {
|
||||
internal.LogAttrs(l.log, slog.LevelDebug, msg, attrs...)
|
||||
}
|
||||
func (l logger) trace(msg string, attrs ...slog.Attr) {
|
||||
internal.LogAttrs(l.log, internal.LevelTrace, msg, attrs...)
|
||||
}
|
||||
|
||||
func debugEthPacket(logger *slog.Logger, prefix string, b []byte) {
|
||||
frm, err := ethernet.NewFrame(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if frm.EtherTypeOrSize() != ethernet.TypeIPv4 {
|
||||
return
|
||||
}
|
||||
ihdr, err := ipv4.NewFrame(frm.Payload())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ihdr.Protocol() != lneto.IPProtoTCP {
|
||||
return
|
||||
}
|
||||
thdr, err := tcp.NewFrame(ihdr.Payload())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(prefix, ihdr.String()+" TCP:"+thdr.String())
|
||||
payload := thdr.Payload()
|
||||
if len(payload) > 0 {
|
||||
fmt.Println("PAYLOAD:", string(payload))
|
||||
}
|
||||
}
|
||||
|
||||
func debugHex(b []byte) string {
|
||||
var d []byte
|
||||
for i := 0; i < len(b); i++ {
|
||||
c1 := tblhex[b[i]&0xf]
|
||||
c2 := tblhex[b[i]>>4]
|
||||
d = append(d, c2, c1, ' ')
|
||||
}
|
||||
return string(d)
|
||||
}
|
||||
|
||||
const tblhex = "0123456789abcdef"
|
||||
|
||||
func getTCPFlags(frames []pcap.Frame, pkt []byte) (flags tcp.Flags) {
|
||||
for i := range frames {
|
||||
if frames[i].Protocol != lneto.IPProtoTCP {
|
||||
continue
|
||||
}
|
||||
iflags, err := frames[i].FieldByClass(pcap.FieldClassFlags)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
v, err := frames[i].FieldAsUint(iflags, pkt)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return tcp.Flags(v)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -48,11 +48,9 @@ func run() (err error) {
|
||||
flagHostToResolve = ""
|
||||
flagRequestedIP = ""
|
||||
flagDoNTP = false
|
||||
flagHTTPGet = false
|
||||
flagNoPcap = false
|
||||
flagPprof = false
|
||||
)
|
||||
flag.BoolVar(&flagHTTPGet, "httpget", flagHTTPGet, "Do an HTTP GET request ")
|
||||
flag.StringVar(&flagInterface, "i", flagInterface, "Interface to use. Either tap* or the name of an existing interface to bridge to.")
|
||||
flag.BoolVar(&flagUseHTTP, "ihttp", flagUseHTTP, "Use HTTP tap interface.")
|
||||
flag.StringVar(&flagHostToResolve, "host", flagHostToResolve, "Hostname to resolve via DNS.")
|
||||
@@ -60,6 +58,10 @@ func run() (err error) {
|
||||
flag.BoolVar(&flagDoNTP, "ntp", flagDoNTP, "Do NTP round and print result time")
|
||||
flag.BoolVar(&flagNoPcap, "nopcap", flagNoPcap, "Disable pcap logging.")
|
||||
flag.BoolVar(&flagPprof, "pprof", flagPprof, "Enable CPU profiling.")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "xcurl is a curl-like command line utility to test lneto's networking features.\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
if flagPprof {
|
||||
var b bytes.Buffer
|
||||
@@ -69,12 +71,12 @@ func run() (err error) {
|
||||
os.WriteFile("xnet.pprof", b.Bytes(), 0777)
|
||||
}()
|
||||
}
|
||||
fmt.Println("softrand", softRand)
|
||||
_, err = dns.NewName(flagHostToResolve)
|
||||
if err != nil {
|
||||
flag.Usage()
|
||||
return err
|
||||
}
|
||||
fmt.Println("softrand", softRand)
|
||||
var iface ltesto.Interface
|
||||
if flagUseHTTP {
|
||||
iface = ltesto.NewHTTPTapClient("http://127.0.0.1:7070")
|
||||
@@ -272,65 +274,65 @@ func run() (err error) {
|
||||
TxBuf: make([]byte, mtu),
|
||||
TxPacketQueueSize: 3,
|
||||
})
|
||||
if flagHTTPGet {
|
||||
timeHTTPCreate := timer("create HTTP GET request")
|
||||
var hdr httpraw.Header
|
||||
hdr.SetMethod("GET")
|
||||
hdr.SetRequestURI("/")
|
||||
hdr.SetProtocol("HTTP/1.1")
|
||||
hdr.Set("Host", flagHostToResolve)
|
||||
hdr.Set("User-Agent", "lneto")
|
||||
hdr.Set("Accept-Language", "en-US,en;q=0.5")
|
||||
hdr.Set("Connection", "close") // Encourage server to close connection after it finishes sending response.
|
||||
req, err := hdr.AppendRequest(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeHTTPCreate()
|
||||
|
||||
timeTCPDial := timer("TCP dial (handshake)")
|
||||
const tcpDialTimeout = 8 * time.Second // Was 60 * time.Minute causing 3.6s sleep per iteration!
|
||||
target := netip.AddrPortFrom(addrs[0], 80)
|
||||
err = rstack.DoDialTCP(&conn, uint16(softRand&0xefff)+1024, target, tcpDialTimeout, internetRetries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TCP failed: %w", err)
|
||||
}
|
||||
timeTCPDial()
|
||||
timeHTTPSend := timer("send HTTP request")
|
||||
conn.SetDeadline(time.Now().Add(internetTimeout))
|
||||
_, err = conn.Write(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conn.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeHTTPSend()
|
||||
timeHTTPRcv := timer("recv http request")
|
||||
|
||||
rxbuf := make([]byte, 2048)
|
||||
|
||||
var page []byte
|
||||
for {
|
||||
var n int
|
||||
n, err = conn.Read(rxbuf)
|
||||
page = append(page, rxbuf[:n]...)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
ptrimmed := bytes.TrimSpace(page)
|
||||
if bytes.EqualFold(ptrimmed[len(ptrimmed)-7:], []byte("</html>")) {
|
||||
// We've received the last part of the HTML. we're done.
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(page) == 0 {
|
||||
return err
|
||||
}
|
||||
timeHTTPRcv()
|
||||
os.Stdout.Write(page)
|
||||
timeHTTPCreate := timer("create HTTP GET request")
|
||||
var hdr httpraw.Header
|
||||
hdr.SetMethod("GET")
|
||||
hdr.SetRequestURI("/")
|
||||
hdr.SetProtocol("HTTP/1.1")
|
||||
hdr.Set("Host", flagHostToResolve)
|
||||
hdr.Set("User-Agent", "lneto")
|
||||
hdr.Set("Accept-Language", "en-US,en;q=0.5")
|
||||
hdr.Set("Connection", "close") // Encourage server to close connection after it finishes sending response.
|
||||
req, err := hdr.AppendRequest(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeHTTPCreate()
|
||||
|
||||
timeTCPDial := timer("TCP dial (handshake)")
|
||||
const tcpDialTimeout = 8 * time.Second // Was 60 * time.Minute causing 3.6s sleep per iteration!
|
||||
target := netip.AddrPortFrom(addrs[0], 80)
|
||||
err = rstack.DoDialTCP(&conn, uint16(softRand&0xefff)+1024, target, tcpDialTimeout, internetRetries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TCP failed: %w", err)
|
||||
}
|
||||
timeTCPDial()
|
||||
timeHTTPSend := timer("send HTTP request")
|
||||
conn.SetDeadline(time.Now().Add(internetTimeout))
|
||||
_, err = conn.Write(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conn.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeHTTPSend()
|
||||
timeHTTPRcv := timer("recv http request")
|
||||
|
||||
rxbuf := make([]byte, 2048)
|
||||
|
||||
var page []byte
|
||||
for {
|
||||
var n int
|
||||
n, err = conn.Read(rxbuf)
|
||||
page = append(page, rxbuf[:n]...)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
ptrimmed := bytes.TrimSpace(page)
|
||||
if bytes.EqualFold(ptrimmed[len(ptrimmed)-7:], []byte("</html>")) {
|
||||
// We've received the last part of the HTML. we're done.
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(page) == 0 {
|
||||
return err
|
||||
}
|
||||
timeHTTPRcv()
|
||||
os.Stdout.Write(page)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user