mirror of
https://github.com/soypat/lneto.git
synced 2026-08-07 16:33:40 +00:00
460 lines
10 KiB
Go
460 lines
10 KiB
Go
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[:], 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(b []byte, _ int) (int, error) {
|
|
n, err := s.link.Encapsulate(b, 0)
|
|
if n > 0 {
|
|
iframes, errpcap := s.shark.CaptureEthernet(s.aux[:0], b[: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(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
|
|
}
|