fix ethernet gateway addr not being set; add IPID field; hardware addr support of tap; better tap interface

This commit is contained in:
soypat
2025-05-31 12:47:09 -03:00
parent 6c65764070
commit 0008791bd8
5 changed files with 187 additions and 43 deletions
+86
View File
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"log/slog"
"net"
"net/http"
"net/netip"
"net/url"
@@ -13,11 +14,14 @@ import (
"github.com/soypat/lneto/internal"
)
const minMTU = 256
// NewHTTPTapClient returns a HTTPTapClient ready for use.
func NewHTTPTapClient(baseURL string) *HTTPTapClient {
var h HTTPTapClient
h.sendurl = baseURL + "/send"
h.recvurl = baseURL + "/recv"
h.infoURL = baseURL + "/info"
_, err := url.Parse(h.sendurl)
if err != nil {
panic(err)
@@ -25,10 +29,59 @@ func NewHTTPTapClient(baseURL string) *HTTPTapClient {
return &h
}
func (h *HTTPTapClient) IPPrefix() netip.Prefix {
h.ensureMTU()
return h.ip
}
func (h *HTTPTapClient) MTU() int {
h.ensureMTU()
return len(h.buf)
}
func (h *HTTPTapClient) HardwareAddr6() [6]byte {
return h.hwaddr
}
func (h *HTTPTapClient) ensureMTU() (err error) {
if len(h.buf) != 0 {
return nil // MTU processed correctly.
}
defer func() {
if err != nil {
err = fmt.Errorf("unable to get MTU from server: %w", err)
}
}()
resp, err := h.c.Get(h.infoURL)
if err != nil {
return err
}
var info TapInfo
err = json.NewDecoder(resp.Body).Decode(&info)
if err != nil {
return err
} else if info.MTU <= minMTU {
return errors.New("small MTU")
}
h.ip, err = netip.ParsePrefix(info.IPPrefix)
if err != nil {
return err
}
h.buf = make([]byte, info.MTU)
hw, err := net.ParseMAC(info.HardwareAddr)
if err == nil {
copy(h.hwaddr[:], hw)
}
return nil
}
type HTTPTapClient struct {
c http.Client
infoURL string
recvurl string
sendurl string
ip netip.Prefix
hwaddr [6]byte
buf []byte
}
@@ -41,6 +94,10 @@ func (h *HTTPTapClient) ReadDiscard() {
}
func (h *HTTPTapClient) Read(b []byte) (int, error) {
err := h.ensureMTU()
if err != nil {
return 0, err
}
resp, err := h.c.Get(h.recvurl)
if err != nil {
return 0, err
@@ -59,6 +116,10 @@ func (h *HTTPTapClient) Read(b []byte) (int, error) {
}
func (h *HTTPTapClient) Write(b []byte) (int, error) {
err := h.ensureMTU()
if err != nil {
return 0, err
}
data, _ := json.Marshal(b)
resp, err := h.c.Post(h.sendurl, "application/json", bytes.NewReader(data))
if err != nil {
@@ -79,7 +140,16 @@ type HTTPTapServer struct {
tapfailed bool
}
type TapInfo struct {
MTU int
IPPrefix string
HardwareAddr string
}
func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int) (*HTTPTapServer, error) {
if mtu < minMTU {
return nil, errors.New("too small MTU")
}
tap, err := internal.NewTap(iface, ip)
if err != nil {
return nil, err
@@ -113,6 +183,18 @@ func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int)
json.NewEncoder(w).Encode("") // send empty string.
}
})
ipstr := ip.String()
sv.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
info := TapInfo{
MTU: mtu,
IPPrefix: ipstr,
}
hw, err := tap.HardwareAddress6()
if err == nil {
info.HardwareAddr = net.HardwareAddr(hw[:]).String()
}
json.NewEncoder(w).Encode(info)
})
taps := HTTPTapServer{
router: sv,
stack: s,
@@ -122,6 +204,10 @@ func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int)
return &taps, nil
}
func (sv *HTTPTapServer) HardwareAddress6() (hwaddr [6]byte, err error) {
return sv.tap.HardwareAddress6()
}
func (sv *HTTPTapServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
sv.router.ServeHTTP(w, r)
}
+57 -10
View File
@@ -13,7 +13,7 @@ import (
)
type Tap struct {
fd int
fd int // points to /dev/net/tun device.
name string
}
@@ -25,19 +25,19 @@ func NewTap(name string, ip netip.Prefix) (*Tap, error) {
if err != nil {
return nil, fmt.Errorf("failed to open tun device: %w", err)
}
var ifr [syscall.IFNAMSIZ + 64]byte // extra space for compatibility
// Set the name; it will be zero-padded automatically.
copy(ifr[:syscall.IFNAMSIZ-1], name)
tap := Tap{
name: name,
fd: fd,
}
ifr := tap.ifreq()
// Set the flags (starting at offset IFNAMSIZ).
flags := uint16(syscall.IFF_TAP | syscall.IFF_NO_PI)
*(*uint16)(unsafe.Pointer(&ifr[syscall.IFNAMSIZ])) = flags
ifr.setflags(flags)
// Issue the ioctl to create the interface.
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TUNSETIFF), uintptr(unsafe.Pointer(&ifr[0])))
if errno != 0 {
return nil, fmt.Errorf("creating tap interface: %w", errno)
err = ioctl(fd, syscall.TUNSETIFF, ifr.ptr())
if err != nil {
return nil, fmt.Errorf("creating tap interface: %w", err)
}
if ip.IsValid() {
// Optionally, bring the interface up and assign an IP address.
@@ -66,3 +66,50 @@ func (tap *Tap) Write(b []byte) (int, error) {
func (tap *Tap) Close() error {
return syscall.Close(tap.fd)
}
func ioctl(fd int, request uintptr, argp unsafe.Pointer) error {
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, uintptr(argp))
if errno != 0 {
return os.NewSyscallError("ioctl", errno)
}
return nil
}
func (tap *Tap) HardwareAddress6() (hw [6]byte, err error) {
// We cannot use tap.sock to query the hardware address, this is something known by the network stack, so get a sock to network stack.
sock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
if err != nil {
return hw, fmt.Errorf("socket open: %w", err)
}
defer syscall.Close(sock)
ifr := tap.ifreq()
err = ioctl(sock, syscall.SIOCGIFHWADDR, ifr.ptr())
if err != nil {
return hw, err
}
sa_family := *(*uint16)(unsafe.Pointer(&ifr.Data[0]))
if sa_family != 1 {
return hw, fmt.Errorf("expecting sa_family=1 got %d", sa_family)
}
copy(hw[:], ifr.Data[2:]) // first two bytes are sa_family
return hw, nil
}
type ifreq struct {
Name [syscall.IFNAMSIZ]byte
Data [64]byte // union data (covers ifr_hwaddr, etc.)
}
func (ifr *ifreq) setflags(flags uint16) {
*(*uint16)(unsafe.Pointer(&ifr.Data[0])) = flags
}
func (ifr *ifreq) ptr() unsafe.Pointer { return unsafe.Pointer(ifr) }
func (tap *Tap) ifreq() ifreq {
// Set the name; it will be zero-padded automatically.
var ifr ifreq
copy(ifr.Name[:], tap.name)
return ifr
}