Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ add ProxyTunnel to replace tun2socks #1700

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@ go 1.20
require (
github.com/Jigsaw-Code/outline-sdk v0.0.2
github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20230807220427-893de7fdc6b8
github.com/eycorsican/go-tun2socks v1.16.11
github.com/stretchr/testify v1.8.4
golang.org/x/mobile v0.0.0-20230818142238-7088062f872d
golang.org/x/sys v0.11.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/eycorsican/go-tun2socks v1.16.11 // indirect
github.com/miekg/dns v1.1.54 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/tools v0.9.1 // indirect
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/tools v0.12.1-0.20230818130535-1517d1a3ba60 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
22 changes: 13 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,32 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/mobile v0.0.0-20230818142238-7088062f872d h1:Ouem7YgI783/xoG5NZUHbg/ggHFOutUUoq1ZRlCCTbM=
golang.org/x/mobile v0.0.0-20230818142238-7088062f872d/go.mod h1:kQNMt2gXlYXNazoSeytBi7knmDN7YS/JzMKFYxgoNxc=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.12.1-0.20230818130535-1517d1a3ba60 h1:o4bs4seAAlSiZQAZbO6/RP5XBCZCooQS3Pgc0AUjWts=
golang.org/x/tools v0.12.1-0.20230818130535-1517d1a3ba60/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
18 changes: 18 additions & 0 deletions outline/client/backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Outline Client Backend

## Environment Preparation

```sh
go install golang.org/x/mobile/cmd/gomobile@latest
export GOBIN="$HOME/go/bin/" -- or your customized go binary folder
export PATH="$GOBIN:$PATH"
gomobile init
```

## Build

```sh
export ANDROID_HOME="$HOME/Android/Sdk"
gomobile clean
gomobile bind -target android ./outline/client/backend
```
55 changes: 55 additions & 0 deletions outline/client/backend/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package backend

import (
"io"
"sync"
)

type Reader interface {
io.Reader
}

type Writer interface {
io.Writer
}

type ReadWriter interface {
Reader
Writer
}

type AsyncCopyResult struct {
wg sync.WaitGroup
copied int64
err error
}

func CopyAsync(dest Writer, source Reader) *AsyncCopyResult {
w := &AsyncCopyResult{}
w.wg.Add(1)
go func() {
defer w.wg.Done()
buf := make([]byte, 1500)
w.copied, w.err = io.CopyBuffer(dest, source, buf)
}()
return w
}

func (w *AsyncCopyResult) Await() (int64, error) {
w.wg.Wait()
return w.copied, w.err
}
94 changes: 94 additions & 0 deletions outline/client/backend/electron_legacy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///go:build linux & windows

package electronlegacy

import (
"flag"
"log"
"os"
"os/signal"
"strings"
"syscall"

"github.com/Jigsaw-Code/outline-apps/outline/client/backend"
"github.com/eycorsican/go-tun2socks/tun"
)

var args struct {
// TUN device settings
tunAddr *string
tunGw *string
tunMask *string
tunName *string
tunDNS *string

// Proxy settings
proxyConfig *string
}

func main() {
args.tunAddr = flag.String("tunAddr", "10.0.85.2", "TUN interface IP address")
args.tunGw = flag.String("tunGw", "10.0.85.1", "TUN interface gateway")
args.tunMask = flag.String("tunMask", "255.255.255.0", "TUN interface network mask; prefixlen for IPv6")
args.tunDNS = flag.String("tunDNS", "1.1.1.1,9.9.9.9,208.67.222.222", "Comma-separated list of DNS resolvers for the TUN interface (Windows only)")
args.tunName = flag.String("tunName", "tun0", "TUN interface name")
args.proxyConfig = flag.String("config", "", "The Outline configuration in JSON format")
flag.Parse()

proxy, err := backend.NewProxyTunnel(*args.proxyConfig)
if err != nil {
log.Fatalf("Failed to create Outline ProxyTunnel: %v", err)
}
log.Println("Outline ProxyTunnel created")
defer log.Println("Outline ProxyTunnel stopped")
defer proxy.Close() // not necessary, but no harm

dnsResolvers := strings.Split(*args.tunDNS, ",")
tunDev, err := tun.OpenTunDevice(*args.tunName, *args.tunAddr, *args.tunGw, *args.tunMask, dnsResolvers, true)
if err != nil {
log.Fatalf("Failed to open tun device: %v", err)
}
log.Println("Tun device opened")
defer log.Println("Tun device stopped")
defer tunDev.Close() // not necessary, but no harm

if err := proxy.Refresh(); err != nil {
log.Fatalf("Failed to connect to proxy: %v", err)
}

log.Println("Copying traffic from proxy to tun device...")
r1 := backend.CopyAsync(tunDev, proxy)
defer func() {
n, err := r1.Await()
log.Printf("Traffic from proxy to tun device stopped: n = %v, err = %v\n", n, err)
}()

log.Println("Copying traffic from tun device to proxy...")
r2 := backend.CopyAsync(proxy, tunDev)
defer func() {
n, err := r2.Await()
log.Printf("Traffic from tun device to proxy stopped: n = %v, err = %v\n", n, err)
}()

osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
sig := <-osSignals

log.Printf("Received signal: %v, terminating...", sig)
proxy.Close()
tunDev.Close()
}
30 changes: 30 additions & 0 deletions outline/client/backend/proxy_tunnel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package backend

import "github.com/Jigsaw-Code/outline-apps/outline/device"

// ProxyTunnel is an interface that will be exported by gomobile, and be used by Outline Client.
type ProxyTunnel struct {
*device.OutlineDevice
}

func NewProxyTunnel(configJSON string) (*ProxyTunnel, error) {
d, err := device.NewOutlineDevice(configJSON)
if err != nil {
return nil, err
}
return &ProxyTunnel{d}, nil
}
25 changes: 25 additions & 0 deletions outline/client/backend/tools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build tools
// +build tools

// See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
// and https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md

package tools

import (
_ "golang.org/x/mobile/cmd/gomobile"
)
38 changes: 38 additions & 0 deletions outline/client/backend/tun_device_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package backend

import (
"errors"
"fmt"
"os"

"golang.org/x/sys/unix"
)

func NewTunDeviceFromFD(fd int) (ReadWriter, error) {
if fd < 0 {
return nil, errors.New("fd is invalid")
}
dupFd, err := unix.Dup(fd)
if err != nil {
return nil, fmt.Errorf("failed to dup fd: %v", err)
}
f := os.NewFile(uintptr(dupFd), "")
if f == nil {
return nil, errors.New("failed to open file from fd")
}
return f, nil
}
14 changes: 6 additions & 8 deletions outline/device/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ const (

// OutlineDevice delegates the TCP and UDP traffic from local machine to the remote Outline server.
type OutlineDevice struct {
t2s network.IPDevice
pp *outlinePacketProxy
sd transport.StreamDialer
// The tun2socks IP device
network.IPDevice

pp *outlinePacketProxy
sd transport.StreamDialer
}

// NewOutlineDevice creates a new [OutlineDevice] that can relay traffic to a remote Outline server.
Expand All @@ -51,17 +53,13 @@ func NewOutlineDevice(configJSON string) (d *OutlineDevice, err error) {
return nil, fmt.Errorf("failed to create UDP proxy: %w", err)
}

if d.t2s, err = lwip2transport.ConfigureDevice(d.sd, d.pp); err != nil {
if d.IPDevice, err = lwip2transport.ConfigureDevice(d.sd, d.pp); err != nil {
return nil, fmt.Errorf("failed to configure lwIP: %w", err)
}

return
}

func (d *OutlineDevice) Close() error {
return d.t2s.Close()
}

func (d *OutlineDevice) Refresh() error {
return d.pp.testConnectivityAndRefresh(connectivityTestDNSResolver, connectivityTestTargetDomain)
}
Loading