mirror of
https://github.com/soypat/lneto.git
synced 2026-08-15 12:23:44 +00:00
324 lines
6.4 KiB
Go
324 lines
6.4 KiB
Go
package ltesto
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"net/url"
|
|
|
|
"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)
|
|
}
|
|
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
|
|
}
|
|
|
|
func (h *HTTPTapClient) ReadDiscard() error {
|
|
for {
|
|
d, _ := h.ReadBytes() // Empty remote data.
|
|
if len(d) == 0 {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *HTTPTapClient) ReadBytes() (data []byte, err error) {
|
|
err = h.ensureMTU()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := h.c.Get(h.recvurl)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if resp.StatusCode != 200 {
|
|
return nil, errors.New(resp.Status + " for " + h.recvurl)
|
|
}
|
|
buf := h.buf
|
|
err = json.NewDecoder(resp.Body).Decode(&buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
func (h *HTTPTapClient) Read(b []byte) (int, error) {
|
|
err := h.ensureMTU()
|
|
if err != nil {
|
|
return 0, err
|
|
} else if len(b) < h.MTU() {
|
|
return 0, errors.New("buffer must have at least MTU size")
|
|
}
|
|
data, err := h.ReadBytes()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
n := copy(b, data)
|
|
return n, nil
|
|
}
|
|
|
|
func (h *HTTPTapClient) Write(b []byte) (int, error) {
|
|
err := h.ensureMTU()
|
|
if err != nil {
|
|
return 0, err
|
|
} else if len(b) > h.MTU() {
|
|
return 0, errors.New("buffer larger than MTU")
|
|
}
|
|
data, _ := json.Marshal(b)
|
|
resp, err := h.c.Post(h.sendurl, "application/json", bytes.NewReader(data))
|
|
if err != nil {
|
|
return 0, err
|
|
} else if resp.StatusCode != 200 {
|
|
return 0, errors.New(resp.Status + " for " + h.sendurl)
|
|
}
|
|
return len(b), nil
|
|
}
|
|
|
|
func (h *HTTPTapClient) Close() error { return nil }
|
|
|
|
type HTTPTapServer struct {
|
|
router *http.ServeMux
|
|
stack stack
|
|
tap *internal.Tap
|
|
buf []byte
|
|
onTx func(channel int, pkt []byte)
|
|
tapfailed bool
|
|
}
|
|
|
|
type tapInfo struct {
|
|
MTU int
|
|
IPPrefix string
|
|
HardwareAddr string
|
|
}
|
|
|
|
func (sv *HTTPTapServer) OnTransfer(cb func(channel int, pkt []byte)) {
|
|
sv.onTx = cb
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
s := stack{
|
|
out: make(chan []byte, queueOut),
|
|
in: make(chan []byte, queueIn),
|
|
}
|
|
sv := http.NewServeMux()
|
|
taps := &HTTPTapServer{
|
|
router: sv,
|
|
stack: s,
|
|
tap: tap,
|
|
buf: make([]byte, mtu),
|
|
}
|
|
sv.HandleFunc("/send", func(w http.ResponseWriter, r *http.Request) {
|
|
var data []byte
|
|
err := json.NewDecoder(r.Body).Decode(&data)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
} else {
|
|
if taps.onTx != nil {
|
|
taps.onTx(1, data)
|
|
}
|
|
select {
|
|
case s.out <- data:
|
|
default:
|
|
http.Error(w, "outgoing packet queue full", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
})
|
|
sv.HandleFunc("/recv", func(w http.ResponseWriter, r *http.Request) {
|
|
select {
|
|
case data := <-s.in:
|
|
json.NewEncoder(w).Encode(data)
|
|
default:
|
|
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)
|
|
})
|
|
|
|
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)
|
|
}
|
|
|
|
func (sv *HTTPTapServer) Close() error {
|
|
return sv.tap.Close()
|
|
}
|
|
|
|
type HandleTapResult struct {
|
|
Failed bool
|
|
SentSize int
|
|
ReceivedSize int
|
|
}
|
|
|
|
func (sv *HTTPTapServer) HandleTap() (result HandleTapResult, err error) {
|
|
result.ReceivedSize, err = sv.readTap()
|
|
result.Failed = sv.tapfailed
|
|
if result.Failed && err != nil {
|
|
return result, err
|
|
}
|
|
var err2 error
|
|
result.ReceivedSize, err2 = sv.writeTap()
|
|
result.Failed = result.Failed || sv.tapfailed
|
|
if err2 != nil && err == nil {
|
|
err = err2
|
|
} else if err2 != nil {
|
|
err = errors.Join(err, err2)
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
func (sv *HTTPTapServer) readTap() (int, error) {
|
|
buf := sv.buf
|
|
n, err := sv.tap.Read(buf[:])
|
|
if err != nil {
|
|
sv.tapfailed = true
|
|
return n, err
|
|
} else if n > 0 {
|
|
if sv.onTx != nil {
|
|
sv.onTx(0, buf[:n])
|
|
}
|
|
err = sv.stack.recv(buf[:n])
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (sv *HTTPTapServer) writeTap() (int, error) {
|
|
buf := sv.buf
|
|
n, err := sv.stack.handle(buf[:])
|
|
if err != nil {
|
|
return n, err
|
|
} else if n > 0 {
|
|
n, err = sv.tap.Write(buf[:n])
|
|
if err != nil {
|
|
sv.tapfailed = true
|
|
return n, err
|
|
}
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
type stack struct {
|
|
out chan []byte
|
|
in chan []byte
|
|
}
|
|
|
|
func (s *stack) recv(b []byte) (err error) {
|
|
bcopy := append([]byte{}, b...)
|
|
RETRY:
|
|
select {
|
|
case s.in <- bcopy:
|
|
default:
|
|
err = errors.New("receive queue packet full, dropping packet")
|
|
<-s.in
|
|
goto RETRY
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *stack) handle(b []byte) (n int, _ error) {
|
|
select {
|
|
case incoming := <-s.out:
|
|
n = copy(b, incoming)
|
|
default:
|
|
// pass if no data available.
|
|
}
|
|
return n, nil
|
|
}
|