diff --git a/endpoints/vpn/backend.go b/endpoints/vpn/backend.go index aeebd0eb..23b18a0a 100644 --- a/endpoints/vpn/backend.go +++ b/endpoints/vpn/backend.go @@ -2,15 +2,19 @@ package vpn import ( "encoding/json" - "fmt" + "github.com/net-byte/vtun/common/config" + "github.com/net-byte/vtun/tun" _ "github.com/net-byte/vtun/tun" + "github.com/net-byte/water" "github.com/openziti/sdk-golang/ziti" "github.com/openziti/sdk-golang/ziti/edge" "github.com/openziti/zrok/endpoints" + cmap "github.com/orcaman/concurrent-map/v2" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/songgao/water/waterutil" "net" + "sync/atomic" "time" ) @@ -21,11 +25,20 @@ type BackendConfig struct { RequestsChan chan *endpoints.Request } +type client struct { + conn net.Conn +} + type Backend struct { cfg *BackendConfig listener edge.Listener cidr net.IPAddr + tun *water.Interface + mtu int + + counter atomic.Uint32 + clients cmap.ConcurrentMap[dest, *client] } func NewBackend(cfg *BackendConfig) (*Backend, error) { @@ -49,14 +62,66 @@ func NewBackend(cfg *BackendConfig) (*Backend, error) { b := &Backend{ cfg: cfg, listener: listener, + mtu: ZROK_VPN_MTU, + clients: cmap.NewWithCustomShardingFunction[dest, *client](func(key dest) uint32 { + return key.toInt32() + }), } + b.counter.Store(1) return b, nil } +func (b *Backend) readTun() { + buf := make([]byte, ZROK_VPN_MTU) + for { + n, err := b.tun.Read(buf) + if err != nil { + logrus.WithError(err).Error("failed to read tun device") + // handle? error + panic(err) + return + } + pkt := packet(buf[:n]) + if !waterutil.IsIPv4(pkt) { + continue + } + + logrus.WithField("packet", pkt).Trace("read from tun device") + dest := pkt.destination() + + if clt, ok := b.clients.Get(dest); ok { + _, err := clt.conn.Write(pkt) + if err != nil { + logrus.WithError(err).Errorf("failed to write packet to clt[%v]", dest) + _ = clt.conn.Close() + b.clients.Remove(dest) + } + } else { + logrus.Errorf("no client with address[%v]", dest) + } + } +} + func (b *Backend) Run() error { logrus.Info("started") defer logrus.Info("exited") + tunCfg := config.Config{ + ServerIP: "192.168.127.1", + ServerIPv6: "fced::ffff:c0a8:7f01", + CIDR: "192.168.127.1/24", + CIDRv6: "fced::ffff:c0a8:7f01/64", + MTU: ZROK_VPN_MTU, + Verbose: true, + } + + b.tun = tun.CreateTun(tunCfg) + defer func() { + _ = b.tun.Close() + }() + + go b.readTun() + for { if conn, err := b.listener.Accept(); err == nil { go b.handle(conn) @@ -66,47 +131,54 @@ func (b *Backend) Run() error { } } -type ipProto waterutil.IPProtocol - -func (p ipProto) String() string { - switch p { - case waterutil.TCP: - return "tcp" - case waterutil.UDP: - return "udp" - case waterutil.ICMP: - return "icmp" - default: - return fmt.Sprintf("proto[%d]", p) - } -} - func (b *Backend) handle(conn net.Conn) { defer func(conn net.Conn) { _ = conn.Close() }(conn) + num := uint32(0) + for num == 0 || num == 1 { + num = b.counter.Add(1) + num = num % 256 + } + + ipv4 := net.IPv4(192, 168, 127, byte(num)) + ip := ipToDest(ipv4) + cfg := &ClientConfig{ Greeting: "Welcome to zrok VPN", + IP: ipv4.String(), + ServerIP: "192.168.127.1", + CIDR: ipv4.String() + "/24", + MTU: b.mtu, } + j, err := json.Marshal(&cfg) if err != nil { + logrus.WithError(err).Error("failed to write client VPN config") + return + } + _, err = conn.Write(j) + if err != nil { + logrus.WithError(err).Error("failed to write client VPN config") return } - conn.Write(j) - buf := make([]byte, 16*1024) + clt := &client{conn: conn} + b.clients.Set(ip, clt) + + buf := make([]byte, b.mtu) for { read, err := conn.Read(buf) if err != nil { logrus.Error("read error", err) return } - pkt := buf[:read] - logrus.Infof("read packet %d bytes %v %v:%v -> %v:%v", read, - ipProto(waterutil.IPv4Protocol(pkt)), - waterutil.IPv4Source(pkt), waterutil.IPv4SourcePort(pkt), - waterutil.IPv4Destination(pkt), waterutil.IPv4DestinationPort(pkt)) - + pkt := packet(buf[:read]) + logrus.WithField("packet", pkt).Info("read from ziti") + _, err = b.tun.Write(pkt) + if err != nil { + logrus.WithError(err).Error("failed to write packet to tun") + } } } diff --git a/endpoints/vpn/vpn.go b/endpoints/vpn/vpn.go index e555d3bb..a2605c6d 100644 --- a/endpoints/vpn/vpn.go +++ b/endpoints/vpn/vpn.go @@ -1,9 +1,63 @@ package vpn +import ( + "fmt" + "github.com/songgao/water/waterutil" + "net" + "strconv" +) + +const ZROK_VPN_MTU = 16 * 1024 + type ClientConfig struct { Greeting string IP string CIDR string ServerIP string Routes []string + MTU int +} + +type dest struct { + addr [4]byte +} + +func (d dest) String() string { + return net.IP(d.addr[:]).String() +} + +func (d dest) toInt32() uint32 { + return uint32(d.addr[0])<<24 + uint32(d.addr[1])<<16 + uint32(d.addr[2])<<8 + uint32(d.addr[3]) +} + +func ipToDest(addr net.IP) dest { + return dest{ + addr: [4]byte{addr[0], addr[1], addr[2], addr[3]}, + } +} + +type packet []byte + +func (p packet) destination() dest { + return ipToDest(waterutil.IPv4Destination(p)) +} + +func (p packet) String() string { + return fmt.Sprintf("%s %s:%d -> %s:%d %d bytes", p.proto(), + waterutil.IPv4Source(p), waterutil.IPv4SourcePort(p), + waterutil.IPv4Destination(p), waterutil.IPv4DestinationPort(p), len(waterutil.IPv4Payload(p))) +} + +func (p packet) proto() string { + proto := waterutil.IPv4Protocol(p) + switch proto { + case waterutil.TCP: + return "tcp" + case waterutil.UDP: + return "udp" + case waterutil.ICMP: + return "icmp" + default: + return strconv.Itoa(int(proto)) + } } diff --git a/go.mod b/go.mod index 13c9c29b..ab29aa07 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/michaelquigley/pfxlog v0.6.10 github.com/muesli/reflow v0.3.0 github.com/net-byte/vtun v1.7.0 + github.com/net-byte/water v0.0.7 github.com/nxadm/tail v1.4.8 github.com/openziti/channel/v2 v2.0.121 github.com/openziti/edge-api v0.26.12 @@ -38,6 +39,7 @@ require ( github.com/openziti/identity v1.0.72 github.com/openziti/sdk-golang v0.23.10 github.com/openziti/transport/v2 v2.0.124 + github.com/orcaman/concurrent-map/v2 v2.0.1 github.com/pkg/errors v0.9.1 github.com/rabbitmq/amqp091-go v1.8.1 github.com/rubenv/sql-migrate v1.6.0 @@ -166,7 +168,6 @@ require ( github.com/muesli/termenv v0.13.0 // indirect github.com/muhlemmer/gu v0.3.1 // indirect github.com/net-byte/go-gateway v0.0.2 // indirect - github.com/net-byte/water v0.0.7 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect @@ -174,7 +175,6 @@ require ( github.com/openziti/metrics v1.2.47 // indirect github.com/openziti/secretstream v0.1.17 // indirect github.com/openziti/storage v0.2.6 // indirect - github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect github.com/parallaxsecond/parsec-client-go v0.0.0-20221025095442-f0a77d263cf9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect