diff --git a/go.mod b/go.mod index 4e74235ce5..4a7e41436c 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,20 @@ 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/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/tools v0.12.1-0.20230818130535-1517d1a3ba60 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 56672c45c6..ad68b33050 100644 --- a/go.sum +++ b/go.sum @@ -17,28 +17,29 @@ 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/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/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= diff --git a/outline/client/backend/README.md b/outline/client/backend/README.md new file mode 100644 index 0000000000..0d054e5af4 --- /dev/null +++ b/outline/client/backend/README.md @@ -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 +``` diff --git a/outline/client/backend/copy.go b/outline/client/backend/copy.go new file mode 100644 index 0000000000..c1add81206 --- /dev/null +++ b/outline/client/backend/copy.go @@ -0,0 +1,50 @@ +// 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 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) Wait() (int64, error) { + w.wg.Wait() + return w.copied, w.err +} diff --git a/outline/client/backend/electron_legacy/main.go b/outline/client/backend/electron_legacy/main.go new file mode 100644 index 0000000000..fb06d9fb25 --- /dev/null +++ b/outline/client/backend/electron_legacy/main.go @@ -0,0 +1,77 @@ +// 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 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 tunDev.Close() // not necessary, but no harm + + defer backend.CopyAsync(tunDev, proxy).Wait() + defer backend.CopyAsync(proxy, tunDev).Wait() + + 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() +} diff --git a/outline/client/backend/proxy_tunnel.go b/outline/client/backend/proxy_tunnel.go new file mode 100644 index 0000000000..1af5e8acf9 --- /dev/null +++ b/outline/client/backend/proxy_tunnel.go @@ -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 +} diff --git a/outline/device/device.go b/outline/device/device.go index 1a9ce70456..aad4a19c1e 100644 --- a/outline/device/device.go +++ b/outline/device/device.go @@ -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. @@ -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) }