Merge pull request #595 from openziti/vpn-mode

VPN share mode
This commit is contained in:
Michael Quigley 2024-04-16 16:34:36 -04:00 committed by GitHub
commit 9182d95621
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 848 additions and 11 deletions

View File

@ -2,6 +2,8 @@
## v0.4.27
FEATURE: New `vpn` backend mode. Use `sudo zrok share private --backend-mode vpn` on the _VPN server_ host, then `sudo zrok access private <token>` on _VPN client_ machine. Works with reserved shares using `zrok reserve private --backend-mode vpn`. Use `<target>` parameter to override default VPN network settings `zrok share private -b vpn 192.168.255.42/24` -- server IP is `192.168.255.42` and VPN netmask will be `192.168.255.0/24`. Client IPs are assigned automatically from netmask range.
CHANGE: Update to OpenZiti SDK (`github.com/openziti/sdk-golang`) at `v0.23.22`.
CHANGE: Added indexes to `environments`, `shares`, and `frontends` tables to improve overall query performance on both PostgreSQL and Sqlite.

View File

@ -9,10 +9,12 @@ command -v swagger >/dev/null 2>&1 || {
command -v openapi >/dev/null 2>&1 || {
echo >&2 "command 'openapi' not installed. see: https://www.npmjs.com/package/openapi-client for installation"
exit 1
}
command -v swagger-codegen 2>&1 || {
echo >&2 "command 'swagger-codegen. see: https://github.com/swagger-api/swagger-codegen for installation"
exit 1
}
scriptPath=$(realpath $0)

View File

@ -8,6 +8,7 @@ import (
"github.com/openziti/zrok/endpoints/proxy"
"github.com/openziti/zrok/endpoints/tcpTunnel"
"github.com/openziti/zrok/endpoints/udpTunnel"
"github.com/openziti/zrok/endpoints/vpn"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/rest_client_zrok"
"github.com/openziti/zrok/rest_client_zrok/share"
@ -165,6 +166,30 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
}
}()
case "vpn":
endpointUrl = &url.URL{
Scheme: "VPN",
}
fe, err := vpn.NewFrontend(&vpn.FrontendConfig{
IdentityName: env.EnvironmentIdentityName(),
ShrToken: args[0],
RequestsChan: requests,
})
if err != nil {
if !panicInstead {
tui.Error("unable to create private access", err)
}
panic(err)
}
go func() {
if err := fe.Run(); err != nil {
if !panicInstead {
tui.Error("error starting access", err)
}
panic(err)
}
}()
default:
cfg := proxy.DefaultFrontendConfig(env.EnvironmentIdentityName())
cfg.ShrToken = shrToken

View File

@ -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"
)
@ -40,7 +42,7 @@ func newReserveCommand() *reserveCommand {
command := &reserveCommand{cmd: cmd}
cmd.Flags().StringVarP(&command.uniqueName, "unique-name", "n", "", "A unique name for the reserved share (defaults to generated identifier)")
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode (public|private: proxy, web, caddy, drive) (private: tcpTunnel, udpTunnel, socks)")
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode (public|private: proxy, web, caddy, drive) (private: tcpTunnel, udpTunnel, socks, vpn)")
cmd.Flags().BoolVarP(&command.jsonOutput, "json-output", "j", false, "Emit JSON describing the created reserved share")
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
cmd.Flags().StringVar(&command.oauthProvider, "oauth-provider", "", "Enable OAuth provider [google, github]")
@ -56,7 +58,7 @@ func newReserveCommand() *reserveCommand {
func (cmd *reserveCommand) run(_ *cobra.Command, args []string) {
shareMode := sdk.ShareMode(args[0])
privateOnlyModes := []string{"tcpTunnel", "udpTunnel", "socks"}
privateOnlyModes := []string{"tcpTunnel", "udpTunnel", "socks", "vpn"}
if shareMode != sdk.PublicShareMode && shareMode != sdk.PrivateShareMode {
tui.Error("invalid sharing mode; expecting 'public' or 'private'", nil)
} else if shareMode == sdk.PublicShareMode && slices.Contains(privateOnlyModes, cmd.backendMode) {
@ -114,8 +116,20 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) {
tui.Error("the 'socks' backend mode does not expect <target>", nil)
}
case "vpn":
if len(args) == 2 {
_, _, err := net.ParseCIDR(args[1])
if err != nil {
tui.Error("the 'vpn' backend expect valid CIDR <target>", err)
}
target = args[1]
} else {
target = vpn.DefaultTarget()
}
default:
tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web, tcpTunnel, udpTunnel, caddy, drive, socks}", cmd.backendMode), nil)
tui.Error(fmt.Sprintf("invalid backend mode '%v'; "+
"expected {proxy, web, tcpTunnel, udpTunnel, caddy, drive, socks, vpn}", cmd.backendMode), nil)
}
env, err := environment.LoadRoot()

View File

@ -9,12 +9,14 @@ import (
"github.com/openziti/zrok/endpoints/socks"
"github.com/openziti/zrok/endpoints/tcpTunnel"
"github.com/openziti/zrok/endpoints/udpTunnel"
"github.com/openziti/zrok/endpoints/vpn"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/environment/env_core"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/openziti/zrok/tui"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net"
"os"
"os/signal"
"syscall"
@ -42,7 +44,7 @@ func newSharePrivateCommand() *sharePrivateCommand {
}
command := &sharePrivateCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...")
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, tcpTunnel, udpTunnel, caddy, drive, socks}")
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, tcpTunnel, udpTunnel, caddy, drive, socks, vpn}")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for <target>")
cmd.Flags().BoolVar(&command.closed, "closed", false, "Enable closed permission mode (see --access-grant)")
@ -105,6 +107,17 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
}
target = "socks"
case "vpn":
if len(args) == 1 {
_, _, err := net.ParseCIDR(args[0])
if err != nil {
tui.Error("the 'vpn' backend expect valid CIDR <target>", 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)
}
@ -318,6 +331,28 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
}
}()
case "vpn":
cfg := &vpn.BackendConfig{
IdentityPath: zif,
EndpointAddress: target,
ShrToken: shr.Token,
RequestsChan: requests,
}
be, err := vpn.NewBackend(cfg)
if err != nil {
if !panicInstead {
tui.Error("error creating VPN backend", err)
}
panic(err)
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running VPN backend: %v", err)
}
}()
default:
tui.Error("invalid backend mode", nil)
}

View File

@ -10,6 +10,7 @@ import (
"github.com/openziti/zrok/endpoints/socks"
"github.com/openziti/zrok/endpoints/tcpTunnel"
"github.com/openziti/zrok/endpoints/udpTunnel"
"github.com/openziti/zrok/endpoints/vpn"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/rest_client_zrok/metadata"
"github.com/openziti/zrok/rest_client_zrok/share"
@ -282,6 +283,28 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
}
}()
case "vpn":
cfg := &vpn.BackendConfig{
IdentityPath: zif,
EndpointAddress: target,
ShrToken: shrToken,
RequestsChan: requests,
}
be, err := vpn.NewBackend(cfg)
if err != nil {
if !panicInstead {
tui.Error("error creating VPN backend", err)
}
panic(err)
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running VPN backend: %v", err)
}
}()
default:
tui.Error("invalid backend mode", nil)
}

View File

@ -0,0 +1,3 @@
-- +migrate Up
alter type backend_mode add value 'vpn';

View File

@ -0,0 +1,76 @@
-- +migrate Up
alter table shares rename to shares_old;
create table shares (
id integer primary key,
environment_id integer constraint fk_environments_shares references environments on delete cascade,
z_id string not null unique,
token string not null,
share_mode string not null,
backend_mode string not null,
frontend_selection string,
frontend_endpoint string,
backend_proxy_endpoint string,
reserved boolean not null default(false),
created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
deleted boolean not null default(false),
permission_mode string not null default('open'),
constraint chk_z_id check (z_id <> ''),
constraint chk_token check (token <> ''),
constraint chk_share_mode check (share_mode == 'public' or share_mode == 'private'),
constraint chk_backend_mode check (backend_mode == 'proxy' or backend_mode == 'web' or backend_mode == 'tcpTunnel' or backend_mode == 'udpTunnel' or backend_mode == 'caddy' or backend_mode == 'drive' or backend_mode == 'socks' or backend_mode == 'vpn')
);
insert into shares select * from shares_old;
drop index shares_token_idx;
create unique index shares_token_idx ON shares(token) WHERE deleted is false;
drop index shares_token_perf_idx;
create index shares_token_perf_idx on shares (token);
drop index shares_environment_id_idx;
create index shares_environment_id_idx on shares (environment_id);
alter table frontends rename to frontends_old;
create table frontends (
id integer primary key,
environment_id integer references environments(id),
token varchar(32) not null unique,
z_id varchar(32) not null,
public_name varchar(64) unique,
url_template varchar(1024),
reserved boolean not null default(false),
created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
deleted boolean not null default(false),
private_share_id integer references shares(id)
);
insert into frontends select * from frontends_old;
drop table frontends_old;
create index frontends_environment_id_idx on frontends (environment_id);
alter table share_limit_journal rename to share_limit_journal_old;
create table share_limit_journal (
id integer primary key,
share_id integer references shares(id),
rx_bytes bigint not null,
tx_bytes bigint not null,
action limit_action_type not null,
created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now'))
);
insert into share_limit_journal select * from share_limit_journal_old;
drop table share_limit_journal_old;
alter table access_grants rename to access_grants_old;
create table access_grants (
id integer primary key,
share_id integer references shares(id),
account_id integer references accounts(id),
created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
deleted boolean not null default(false)
);
insert into access_grants select * from access_grants_old;
drop table access_grants_old;
drop table shares_old;

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

120
docs/guides/vpn/vpn.md Normal file
View File

@ -0,0 +1,120 @@
---
sidebar_label: VPN
---
# zrok VPN Guide
zrok VPN backend allows for simple host-to-host VPN setup.
## Starting VPN server
VPN is shared through the `vpn` backend of `zrok` command.
```
eugene@hermes $ sudo -E zrok share private --headless --backend-mode vpn
[ 0.542] INFO sdk-golang/ziti.(*listenerManager).createSessionWithBackoff: {session token=[589d443c-f59d-4fc8-8c48-76609b7fb402]} new service session
[ 0.705] INFO main.(*sharePrivateCommand).run: allow other to access your share with the following command:
zrok access private 3rq7torslq3n
[ 0.705] INFO zrok/endpoints/vpn.(*Backend).Run: started
```
![VPN share](./vpn-share.png)
`sudo` or equivalent invocation is required because VPN mode needs to create a virtual network device (`tun`)
`-E` option allows `zrok` to find your zrok configuration files (in your `$HOME/.zrok`)
By default `vpn` backend uses subnet `10.122.0.0/16` and assigns `10.122.0.1` to the host that stared VPN share.
```
$ ifconfig
tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 16384
inet 10.122.0.1 netmask 255.255.0.0 destination 10.122.0.1
inet6 fe80::705f:24e4:dcfc:a6b2 prefixlen 64 scopeid 0x20<link>
inet6 fd00:7a72:6f6b::1 prefixlen 64 scopeid 0x0<global>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 27 bytes 3236 (3.2 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
```
Default IP/subnet setting can be overridden by adding `<target>` parameter:
```
$ sudo -E zrok share private --headless --backend-mode vpn 192.168.42.12/24
```
## VPN share reservation
Share reservation works the same as with other backend types:
```
eugene@hermes $ zrok reserve private -b vpn
[ 0.297] INFO main.(*reserveCommand).run: your reserved share token is 'k77y2cl7jmjl'
eugene@hermes $ sudo -E zrok share reserved k77y2cl7jmjl --headless
[ 0.211] INFO main.(*shareReservedCommand).run: sharing target: '10.122.0.1/16'
[ 0.211] INFO main.(*shareReservedCommand).run: using existing backend proxy endpoint: 10.122.0.1/16
[ 0.463] INFO sdk-golang/ziti.(*listenerManager).createSessionWithBackoff: {session token=[22c5708d-e2f2-41aa-a507-454055f8bfcc]} new service session
[ 0.641] INFO main.(*shareReservedCommand).run: use this command to access your zrok share: 'zrok access private k77y2cl7jmjl'
[
```
## Accessing VPN share
Accessing a VPN share works similar to other backends.
```
eugene@calculon % sudo -E zrok access private --headless k77y2cl7jmjl
[ 0.201] INFO main.(*accessPrivateCommand).run: allocated frontend '50B5hloP1s1X'
[ 0.662] INFO main.(*accessPrivateCommand).run: access the zrok share at the following endpoint: VPN:
[ 0.662] INFO main.(*accessPrivateCommand).run: 10.122.0.1 -> CONNECTED Welcome to zrok VPN
[ 0.662] INFO zrok/endpoints/vpn.(*Frontend).Run: connected:Welcome to zrok VPN
```
Starting `zrok access` to a VPN share creates virtual network device/interface:
```
eugene@calculon ~ % ifconfig
...
utun5: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
inet 10.122.0.3 --> 10.122.0.1 netmask 0xff000000
inet6 fe80::ce08:faff:fe8a:7b25%utun5 prefixlen 64 scopeid 0x14
nd6 options=201<PERFORMNUD,DAD>
...
```
At this point a VPN tunnel is active between your server and client.
In the example above server is `hermes(10.122.0.1)` and client is `calculon(10.122.0.3)`.
You can access server from client by using assigned IP address.
```
eugene@calculon ~ % ssh eugene@10.122.0.1
Welcome to Ubuntu 23.10 (GNU/Linux 6.5.0-27-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
0 updates can be applied immediately.
Last login: Tue Apr 16 09:27:13 2024 from 127.0.0.1
eugene@hermes:~$ who am i
eugene pts/8 2024-04-16 10:04 (10.122.0.3)
eugene@hermes:~$
```
You can also make a reverse(server-to-client) connection:
```
eugene@hermes:~$ ssh 10.122.0.3
The authenticity of host '10.122.0.3 (10.122.0.3)' can't be established.
<..snip..>
Warning: Permanently added '10.122.0.3' (ED25519) to the list of known hosts.
(eugene@10.122.0.3) Password:
Last login: Tue Apr 16 09:57:28 2024
eugene@calculon ~ % who am i
eugene ttys008 Apr 16 10:06 (10.122.0.1)
eugene@calculon ~ %
```

267
endpoints/vpn/backend.go Normal file
View File

@ -0,0 +1,267 @@
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"
"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"
"io"
"net"
"strconv"
"sync/atomic"
"time"
)
type BackendConfig struct {
IdentityPath string
EndpointAddress string
ShrToken string
RequestsChan chan *endpoints.Request
}
type client struct {
conn net.Conn
}
type Backend struct {
cfg *BackendConfig
listener edge.Listener
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]
}
func NewBackend(cfg *BackendConfig) (*Backend, error) {
options := ziti.ListenOptions{
ConnectTimeout: 5 * time.Minute,
WaitForNEstablishedListeners: 1,
}
zcfg, err := ziti.NewConfigFromFile(cfg.IdentityPath)
if err != nil {
return nil, errors.Wrap(err, "error loading config")
}
zctx, err := ziti.NewContext(zcfg)
if err != nil {
return nil, errors.Wrap(err, "error loading ziti context")
}
listener, err := zctx.ListenWithOptions(cfg.ShrToken, &options)
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,
mtu: ZROK_VPN_MTU,
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
}
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 {
b.cfg.RequestsChan <- &endpoints.Request{
Stamp: time.Now(),
RemoteAddr: dest.String(),
Method: "DISCONNECTED",
}
logrus.WithError(err).Errorf("failed to write packet to clt[%v]", dest)
_ = clt.conn.Close()
b.clients.Remove(dest)
}
} else {
if b.subnet.Contains(net.IP(dest.addr[:])) {
logrus.Errorf("no client with address[%v]", dest)
}
}
}
}
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: 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()
}()
go b.readTun()
for {
if conn, err := b.listener.Accept(); err == nil {
go b.handle(conn)
} else {
return err
}
}
}
func (b *Backend) handle(conn net.Conn) {
defer func(conn net.Conn) {
_ = conn.Close()
}(conn)
ipv4, ipv6 := b.nextIP()
ip := ipToDest(ipv4)
bits, _ := b.subnet.Mask.Size()
bits6, _ := b.subnet6.Mask.Size()
cfg := &ClientConfig{
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{
Stamp: time.Now(),
RemoteAddr: ipv4.String(),
Method: "CONNECTED",
Path: cfg.ServerIP,
}
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
}
clt := &client{conn: conn}
b.clients.Set(ip, clt)
buf := make([]byte, b.mtu)
for {
read, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
logrus.WithError(err).Error("read error")
}
b.cfg.RequestsChan <- &endpoints.Request{
Stamp: time.Now(),
RemoteAddr: ipv4.String(),
Method: "DISCONNECTED",
}
return
}
pkt := packet(buf[:read])
logrus.WithField("packet", pkt).Trace("read from ziti")
_, err = b.tun.Write(pkt)
if err != nil {
logrus.WithError(err).Error("failed to write packet to tun")
return
}
}
}
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
}

131
endpoints/vpn/frontend.go Normal file
View File

@ -0,0 +1,131 @@
package vpn
import (
"encoding/json"
"github.com/net-byte/vtun/common/config"
"github.com/net-byte/vtun/tun"
"github.com/openziti/sdk-golang/ziti"
"github.com/openziti/zrok/endpoints"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"net"
"time"
)
type FrontendConfig struct {
IdentityName string
ShrToken string
RequestsChan chan *endpoints.Request
}
type Frontend struct {
cfg *FrontendConfig
ztx ziti.Context
conn net.Conn
}
func NewFrontend(cfg *FrontendConfig) (*Frontend, error) {
env, err := environment.LoadRoot()
if err != nil {
return nil, errors.Wrap(err, "error loading environment root")
}
zCfgPath, err := env.ZitiIdentityNamed(cfg.IdentityName)
if err != nil {
return nil, errors.Wrapf(err, "error getting ziti identity '%v' from environment", cfg.IdentityName)
}
zCfg, err := ziti.NewConfigFromFile(zCfgPath)
if err != nil {
return nil, errors.Wrap(err, "error loading config")
}
zCfg.ConfigTypes = []string{sdk.ZrokProxyConfig}
zCtx, err := ziti.NewContext(zCfg)
if err != nil {
return nil, errors.Wrap(err, "error loading ziti context")
}
zConn, err := zCtx.Dial(cfg.ShrToken)
if err != nil {
zCtx.Close()
return nil, errors.Wrap(err, "error connecting to ziti")
}
return &Frontend{
cfg: cfg,
ztx: zCtx,
conn: zConn,
}, nil
}
func (f *Frontend) Run() error {
var cltCfg ClientConfig
d := json.NewDecoder(f.conn)
if err := d.Decode(&cltCfg); err != nil {
return errors.Wrap(err, "error decoding vpn config")
}
f.cfg.RequestsChan <- &endpoints.Request{
Stamp: time.Now(),
RemoteAddr: cltCfg.ServerIP,
Method: "CONNECTED",
Path: cltCfg.Greeting,
}
logrus.Info("connected:", cltCfg.Greeting)
defer func() {
f.cfg.RequestsChan <- &endpoints.Request{
Stamp: time.Now(),
RemoteAddr: cltCfg.ServerIP,
Method: "Disconnected",
}
}()
cfg := config.Config{
ServerIP: cltCfg.ServerIP,
CIDR: cltCfg.CIDR,
ServerIPv6: cltCfg.ServerIPv6,
CIDRv6: cltCfg.CIDR6,
MTU: cltCfg.MTU,
Verbose: false,
}
iface := tun.CreateTun(cfg)
logrus.Infof("created tun device: %s", iface.Name())
go func() {
defer func() {
_ = f.conn.Close()
_ = iface.Close()
}()
b := make([]byte, cltCfg.MTU)
for {
n, err := f.conn.Read(b)
if err != nil {
logrus.WithError(err).Error("error reading from ziti")
return
}
p := packet(b[:n])
logrus.WithField("packet", p).Trace("received packet from peer")
_, err = iface.Write(p)
if err != nil {
logrus.WithError(err).Error("error writing to device")
return
}
}
}()
buf := make([]byte, cltCfg.MTU)
for {
n, err := iface.Read(buf)
if err != nil {
return errors.Wrap(err, "error reading packet")
}
pkt := packet(buf[:n])
logrus.WithField("packet", pkt).Trace("read packet from tun device")
_, err = f.conn.Write(pkt)
if err != nil {
return errors.Wrap(err, "error sending packet to ziti")
}
}
}

94
endpoints/vpn/vpn.go Normal file
View File

@ -0,0 +1,94 @@
package vpn
import (
"fmt"
"github.com/songgao/water/waterutil"
"net"
"strconv"
)
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
CIDR string
CIDR6 string
ServerIP string
ServerIPv6 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 {
d := dest{}
copy(d.addr[:], addr.To4())
return d
}
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))
}
}

14
go.mod
View File

@ -17,6 +17,7 @@ require (
github.com/go-openapi/validate v0.24.0
github.com/gobwas/glob v0.2.3
github.com/golang-jwt/jwt/v5 v5.2.1
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
@ -30,6 +31,8 @@ require (
github.com/michaelquigley/cf v0.0.13
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.128
github.com/openziti/edge-api v0.26.16
@ -37,11 +40,13 @@ require (
github.com/openziti/identity v1.0.75
github.com/openziti/sdk-golang v0.23.22
github.com/openziti/transport/v2 v2.0.131
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
github.com/shirou/gopsutil/v3 v3.24.3
github.com/sirupsen/logrus v1.9.3
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
github.com/wneessen/go-mail v0.2.7
@ -107,6 +112,9 @@ require (
github.com/go-resty/resty/v2 v2.12.0 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.1.0 // indirect
github.com/golang/glog v1.1.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
@ -123,6 +131,7 @@ require (
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
@ -159,6 +168,7 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
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/oklog/ulid v1.3.1 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
@ -166,7 +176,6 @@ require (
github.com/openziti/metrics v1.2.51 // indirect
github.com/openziti/secretstream v0.1.19 // 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
@ -230,6 +239,9 @@ require (
golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.13.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/grpc v1.59.0 // indirect

23
go.sum
View File

@ -301,6 +301,12 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -481,6 +487,8 @@ github.com/influxdata/influxdb-client-go/v2 v2.11.0/go.mod h1:YteV91FiQxRdccyJ2c
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 h1:X3Xxno5Ji8idrNiUoFc7QyXpqhSYlDRYQmc7mlpMBzU=
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743/go.mod h1:KrtyD5PFj++GKkFS/7/RRrfnRhAMGQwy75GLCHWrCNs=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
@ -704,6 +712,12 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/net-byte/go-gateway v0.0.2 h1:xNB7CqWh7js6PB/xOochjyJlDHl6sZthhPSoJdxwoLY=
github.com/net-byte/go-gateway v0.0.2/go.mod h1:+NvPbRjN64RUYvm6xtRBUswoAXKAe44Y/PfWtWMgwwY=
github.com/net-byte/vtun v1.7.0 h1:mt8sUKbFPbl5pc0EXeRFl5zCKXr3QCaSYZp7raGd5y0=
github.com/net-byte/vtun v1.7.0/go.mod h1:SVFWdwuyvV7BRyeyiaay7HfWor6se5894IkNsNTTo54=
github.com/net-byte/water v0.0.7 h1:wJUVq8x1AdfzZ5G8Qv61YRX+d22wx0ZgWNG0ihLqglo=
github.com/net-byte/water v0.0.7/go.mod h1:tRTm034ul8JBKkYFGN/WrnUM4cctr9laq5IpwvaVqUE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@ -885,6 +899,8 @@ github.com/smallstep/truststore v0.12.1/go.mod h1:M4mebeNy28KusGX3lJxpLARIktLcyq
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
@ -1257,6 +1273,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1389,6 +1406,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=

View File

@ -31,7 +31,7 @@ type ShareRequest struct {
AuthUsers []*AuthUser `json:"authUsers"`
// backend mode
// Enum: [proxy web tcpTunnel udpTunnel caddy drive socks]
// Enum: [proxy web tcpTunnel udpTunnel caddy drive socks vpn]
BackendMode string `json:"backendMode,omitempty"`
// backend proxy endpoint
@ -128,7 +128,7 @@ var shareRequestTypeBackendModePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["proxy","web","tcpTunnel","udpTunnel","caddy","drive","socks"]`), &res); err != nil {
if err := json.Unmarshal([]byte(`["proxy","web","tcpTunnel","udpTunnel","caddy","drive","socks","vpn"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
@ -158,6 +158,9 @@ const (
// ShareRequestBackendModeSocks captures enum value "socks"
ShareRequestBackendModeSocks string = "socks"
// ShareRequestBackendModeVpn captures enum value "vpn"
ShareRequestBackendModeVpn string = "vpn"
)
// prop value enum

View File

@ -1589,7 +1589,8 @@ func init() {
"udpTunnel",
"caddy",
"drive",
"socks"
"socks",
"vpn"
]
},
"backendProxyEndpoint": {
@ -3344,7 +3345,8 @@ func init() {
"udpTunnel",
"caddy",
"drive",
"socks"
"socks",
"vpn"
]
},
"backendProxyEndpoint": {

View File

@ -11,6 +11,8 @@ const (
UdpTunnelBackendMode BackendMode = "udpTunnel"
CaddyBackendMode BackendMode = "caddy"
DriveBackendMode BackendMode = "drive"
SocksBackendMode BackendMode = "socks"
VpnBackendMode BackendMode = "vpn"
)
type ShareMode string

View File

@ -7,6 +7,9 @@ WEB_BACKEND_MODE: BackendMode = "web"
TCP_TUNNEL_BACKEND_MODE: BackendMode = "tcpTunnel"
UDP_TUNNEL_BACKEND_MODE: BackendMode = "udpTunnel"
CADDY_BACKEND_MODE: BackendMode = "caddy"
DRIVE_BACKEND_MODE: BackendMode = "drive"
SOCKS_BACKEND_MODE: BackendMode = "socks"
VPN_BACKEND_MODE: BackendMode = "vpn"
ShareMode = str

View File

@ -194,7 +194,7 @@ class ShareRequest(object):
:param backend_mode: The backend_mode of this ShareRequest. # noqa: E501
:type: str
"""
allowed_values = ["proxy", "web", "tcpTunnel", "udpTunnel", "caddy", "drive", "socks"] # noqa: E501
allowed_values = ["proxy", "web", "tcpTunnel", "udpTunnel", "caddy", "drive", "socks", "vpn"] # noqa: E501
if backend_mode not in allowed_values:
raise ValueError(
"Invalid value for `backend_mode` ({0}), must be one of {1}" # noqa: E501

View File

@ -1039,7 +1039,7 @@ definitions:
type: string
backendMode:
type: string
enum: ["proxy", "web", "tcpTunnel", "udpTunnel", "caddy", "drive", "socks"]
enum: ["proxy", "web", "tcpTunnel", "udpTunnel", "caddy", "drive", "socks", "vpn"]
backendProxyEndpoint:
type: string
authScheme: