diff --git a/cmd/zrok/reserve.go b/cmd/zrok/reserve.go index ab797059..43890584 100644 --- a/cmd/zrok/reserve.go +++ b/cmd/zrok/reserve.go @@ -3,12 +3,14 @@ package main import ( "encoding/json" "fmt" + "github.com/openziti/zrok/endpoints/vpn" "github.com/openziti/zrok/environment" "github.com/openziti/zrok/sdk/golang/sdk" "github.com/openziti/zrok/tui" "github.com/openziti/zrok/util" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "net" "slices" "time" ) @@ -115,7 +117,15 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) { } case "vpn": - target = "vpn" + if len(args) == 2 { + _, _, err := net.ParseCIDR(args[1]) + if err != nil { + tui.Error("the 'vpn' backend expect valid CIDR ", err) + } + target = args[1] + } else { + target = vpn.DefaultTarget() + } default: tui.Error(fmt.Sprintf("invalid backend mode '%v'; "+ diff --git a/cmd/zrok/sharePrivate.go b/cmd/zrok/sharePrivate.go index f327de81..fe71eb2a 100644 --- a/cmd/zrok/sharePrivate.go +++ b/cmd/zrok/sharePrivate.go @@ -16,6 +16,7 @@ import ( "github.com/openziti/zrok/tui" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "net" "os" "os/signal" "syscall" @@ -107,7 +108,15 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) { target = "socks" case "vpn": - target = "vpn" + if len(args) == 1 { + _, _, err := net.ParseCIDR(args[0]) + if err != nil { + tui.Error("the 'vpn' backend expect valid CIDR ", err) + } + target = args[0] + } else { + target = vpn.DefaultTarget() + } default: tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web, tcpTunnel, udpTunnel, caddy, drive}", cmd.backendMode), nil) diff --git a/endpoints/vpn/backend.go b/endpoints/vpn/backend.go index bc5b5501..34ab4d6c 100644 --- a/endpoints/vpn/backend.go +++ b/endpoints/vpn/backend.go @@ -2,6 +2,7 @@ package vpn import ( "encoding/json" + "github.com/google/go-cmp/cmp" "github.com/net-byte/vtun/common/config" "github.com/net-byte/vtun/tun" _ "github.com/net-byte/vtun/tun" @@ -15,6 +16,7 @@ import ( "github.com/songgao/water/waterutil" "io" "net" + "strconv" "sync/atomic" "time" ) @@ -34,9 +36,12 @@ type Backend struct { cfg *BackendConfig listener edge.Listener - cidr net.IPAddr - tun *water.Interface - mtu int + addr net.IP + addr6 net.IP + subnet *net.IPNet + subnet6 *net.IPNet + tun *water.Interface + mtu int counter atomic.Uint32 clients cmap.ConcurrentMap[dest, *client] @@ -60,6 +65,19 @@ func NewBackend(cfg *BackendConfig) (*Backend, error) { if err != nil { return nil, errors.Wrap(err, "error listening") } + + addr6 := zrokIPv6Addr + addr4 := zrokIPv4Addr + sub4 := zrokIPv4 + sub6 := zrokIPv6 + + if cfg.EndpointAddress != "" { + addr4, sub4, err = net.ParseCIDR(cfg.EndpointAddress) + if err != nil { + return nil, errors.Wrap(err, "failed to parse VPN subnet config") + } + } + b := &Backend{ cfg: cfg, listener: listener, @@ -67,6 +85,10 @@ func NewBackend(cfg *BackendConfig) (*Backend, error) { clients: cmap.NewWithCustomShardingFunction[dest, *client](func(key dest) uint32 { return key.toInt32() }), + addr: addr4, + addr6: addr6, + subnet: sub4, + subnet6: sub6, } b.counter.Store(1) return b, nil @@ -112,15 +134,18 @@ func (b *Backend) Run() error { logrus.Info("started") defer logrus.Info("exited") + bits, _ := b.subnet.Mask.Size() + bits6, _ := b.subnet6.Mask.Size() + 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", + ServerIP: b.addr.String(), + ServerIPv6: b.addr6.String(), + CIDR: b.addr.String() + "/" + strconv.Itoa(bits), + CIDRv6: b.addr6.String() + "/" + strconv.Itoa(bits6), MTU: ZROK_VPN_MTU, Verbose: true, } - + logrus.Infof("%+v", tunCfg) b.tun = tun.CreateTun(tunCfg) defer func() { _ = b.tun.Close() @@ -142,21 +167,19 @@ func (b *Backend) handle(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)) + ipv4, ipv6 := b.nextIP() ip := ipToDest(ipv4) + bits, _ := b.subnet.Mask.Size() + bits6, _ := b.subnet6.Mask.Size() + cfg := &ClientConfig{ - Greeting: "Welcome to zrok VPN", - IP: ipv4.String(), - ServerIP: "192.168.127.1", - CIDR: ipv4.String() + "/24", - MTU: b.mtu, + Greeting: "Welcome to zrok VPN", + ServerIP: b.addr.String(), + ServerIPv6: b.addr6.String(), + CIDR: ipv4.String() + "/" + strconv.Itoa(bits), + CIDR6: ipv6.String() + "/" + strconv.Itoa(bits6), + MTU: b.mtu, } b.cfg.RequestsChan <- &endpoints.Request{ @@ -203,3 +226,40 @@ func (b *Backend) handle(conn net.Conn) { } } } + +func (b *Backend) nextIP() (net.IP, net.IP) { + ip4 := make([]byte, len(b.subnet.IP)) + for { + copy(ip4, b.subnet.IP) + n := b.counter.Add(1) + if n == 0 { + continue + } + + for i := 0; i < len(ip4); i++ { + b := (n >> (i * 8)) % 0xff + ip4[len(ip4)-1-i] ^= byte(b) + } + + // subnet overflow + if !b.subnet.Contains(ip4) { + b.counter.Store(1) + continue + } + + if cmp.Equal(b.addr, ip4) { + continue + } + + if b.clients.Has(ipToDest(ip4)) { + continue + } + + break + } + + ip6 := append([]byte{}, b.subnet6.IP...) + copy(ip6[net.IPv6len-net.IPv4len:], ip4) + + return ip4, ip6 +} diff --git a/endpoints/vpn/frontend.go b/endpoints/vpn/frontend.go index 62e7bf7f..cab11df2 100644 --- a/endpoints/vpn/frontend.go +++ b/endpoints/vpn/frontend.go @@ -82,8 +82,8 @@ func (f *Frontend) Run() error { cfg := config.Config{ ServerIP: cltCfg.ServerIP, CIDR: cltCfg.CIDR, - ServerIPv6: "fced::ffff:c0a8:7f01", - CIDRv6: "fced::ffff:c0a8:7f16/64", + ServerIPv6: cltCfg.ServerIPv6, + CIDRv6: cltCfg.CIDR6, MTU: cltCfg.MTU, Verbose: false, } diff --git a/endpoints/vpn/vpn.go b/endpoints/vpn/vpn.go index 242809c9..1013722d 100644 --- a/endpoints/vpn/vpn.go +++ b/endpoints/vpn/vpn.go @@ -9,13 +9,44 @@ import ( const ZROK_VPN_MTU = 16 * 1024 +var ( + zrokIPv4Addr = net.IPv4(10, 'z', 0, 0) + zrokIPv4 = &net.IPNet{ + IP: net.IPv4(10, 'z', 0, 0), + Mask: net.CIDRMask(16, 8*net.IPv4len), + } + + zrokIPv6Addr = net.IP{0xfd, 0, 'z', 'r', 'o', 'k', 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + zrokIPv6 = &net.IPNet{ + IP: net.IP{0xfd, 0, 'z', 'r', 'o', 'k', // prefix + global ID + 0, 0, // subnet id + 0, 0, 0, 0, + 0, 0, 0, 0, + }, + Mask: net.CIDRMask(64, 8*net.IPv6len), + } +) + +func DefaultTarget() string { + l := len(zrokIPv4Addr) + subnet := net.IPNet{ + IP: make([]byte, l), + Mask: zrokIPv4.Mask, + } + + copy(subnet.IP, zrokIPv4Addr) + subnet.IP[l-1] = 1 + return subnet.String() +} + type ClientConfig struct { - Greeting string - IP string - CIDR string - ServerIP string - Routes []string - MTU int + Greeting string + CIDR string + CIDR6 string + ServerIP string + ServerIPv6 string + Routes []string + MTU int } type dest struct { diff --git a/go.mod b/go.mod index ab29aa07..5703bb4d 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/go-openapi/validate v0.23.0 github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt/v5 v5.2.0 + github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.1 github.com/iancoleman/strcase v0.2.0