Skip to content
This repository was archived by the owner on May 6, 2024. It is now read-only.

Commit 37e9419

Browse files
authored
refactor(intra): use outline-sdk as the network stack (#125)
This PR replaces the `go-tun2socks/core` with `outline-sdk/*` in the Intra project, and removes the dependency on `tunnel.Tunnel`, making the `intra` package a standalone Go package that can be copied to other locations, such as the [Intra repository](https://github.com/Jigsaw-Code/Intra). I kept the signatures of all exported interfaces the same. We can refactor them after we move the code to the Intra repository. Related PR: Jigsaw-Code/outline-sdk#66
1 parent dada265 commit 37e9419

File tree

12 files changed

+699
-385
lines changed

12 files changed

+699
-385
lines changed

go.mod

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@ go 1.18
55
require (
66
github.com/Jigsaw-Code/choir v1.0.1
77
github.com/Jigsaw-Code/getsni v1.0.0
8-
github.com/Jigsaw-Code/outline-sdk v0.0.2
8+
github.com/Jigsaw-Code/outline-sdk v0.0.7
99
github.com/crazy-max/xgo v0.26.0
1010
github.com/eycorsican/go-tun2socks v1.16.11
11-
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c
12-
golang.org/x/net v0.8.0
13-
golang.org/x/sys v0.6.0
11+
golang.org/x/mobile v0.0.0-20230906132913-2077a3224571
12+
golang.org/x/net v0.15.0
13+
golang.org/x/sys v0.12.0
1414
)
1515

1616
require (
1717
github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect
1818
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
19-
golang.org/x/crypto v0.7.0 // indirect
20-
golang.org/x/mod v0.9.0 // indirect
21-
golang.org/x/sync v0.1.0 // indirect
22-
golang.org/x/tools v0.7.0 // indirect
19+
golang.org/x/crypto v0.13.0 // indirect
20+
golang.org/x/mod v0.12.0 // indirect
21+
golang.org/x/sync v0.3.0 // indirect
22+
golang.org/x/tools v0.13.0 // indirect
2323
)

go.sum

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ github.com/Jigsaw-Code/choir v1.0.1 h1:WeRt6aTn5L+MtRNqRJ+J1RKgoO8CyXXt1dtZghy2K
22
github.com/Jigsaw-Code/choir v1.0.1/go.mod h1:c4Wd1y1PeCajZbKZV+ZmcFGMDoduyqMCEMHW5iqzWXI=
33
github.com/Jigsaw-Code/getsni v1.0.0 h1:OUTIu7wTBi/7DMX+RkZrN7XhU3UDevTEsAWK4gsqSwE=
44
github.com/Jigsaw-Code/getsni v1.0.0/go.mod h1:Ps0Ec3fVMKLyAItVbMKoQFq1lDjtFQXZ+G5nRNNh/QE=
5-
github.com/Jigsaw-Code/outline-sdk v0.0.2 h1:uCuyJMaWj57IYEG/Hdml8YMdk9chU60ZkSxJXBhyGHU=
6-
github.com/Jigsaw-Code/outline-sdk v0.0.2/go.mod h1:hhlKz0+r9wSDFT8usvN8Zv/BFToCIFAUn1P2Qk8G2CM=
5+
github.com/Jigsaw-Code/outline-sdk v0.0.7 h1:WlFaV1tFpIQ/pflrKwrQuNIP3kJpgh7yJuqiTb54sGA=
6+
github.com/Jigsaw-Code/outline-sdk v0.0.7/go.mod h1:hhlKz0+r9wSDFT8usvN8Zv/BFToCIFAUn1P2Qk8G2CM=
77
github.com/crazy-max/xgo v0.26.0 h1:vK4OfeXJoDGvnjlzdTCgPbeWLKENbzj84DTpU/VRonM=
88
github.com/crazy-max/xgo v0.26.0/go.mod h1:m/aqfKaN/cYzfw+Pzk7Mk0tkmShg3/rCS4Zdhdugi4o=
99
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -21,25 +21,25 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
2121
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
2222
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
2323
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
24-
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
25-
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
26-
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4=
27-
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
28-
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
29-
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
24+
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
25+
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
26+
golang.org/x/mobile v0.0.0-20230906132913-2077a3224571 h1:QDvQ2KLFHHQWRID6IkZOBf6uLIh9tZ0G+mw61pFQxuo=
27+
golang.org/x/mobile v0.0.0-20230906132913-2077a3224571/go.mod h1:wEyOn6VvNW7tcf+bW/wBz1sehi2s2BZ4TimyR7qZen4=
28+
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
29+
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
3030
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
3131
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
32-
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
33-
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
34-
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
35-
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
32+
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
33+
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
34+
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
35+
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
3636
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
3737
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
3838
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
39-
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
40-
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
39+
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
40+
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
4141
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
4242
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
43-
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
44-
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
43+
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
44+
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
4545
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

intra/android/init.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package tun2socks
2+
3+
// Copyright 2019 The Outline Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
import (
18+
"runtime/debug"
19+
20+
"github.com/eycorsican/go-tun2socks/common/log"
21+
)
22+
23+
func init() {
24+
// Conserve memory by increasing garbage collection frequency.
25+
debug.SetGCPercent(10)
26+
log.SetLevel(log.WARN)
27+
}

intra/android/tun.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2023 Jigsaw Operations LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package tun2socks
16+
17+
import (
18+
"errors"
19+
"os"
20+
21+
"golang.org/x/sys/unix"
22+
)
23+
24+
func makeTunFile(fd int) (*os.File, error) {
25+
if fd < 0 {
26+
return nil, errors.New("must provide a valid TUN file descriptor")
27+
}
28+
// Make a copy of `fd` so that os.File's finalizer doesn't close `fd`.
29+
newfd, err := unix.Dup(fd)
30+
if err != nil {
31+
return nil, err
32+
}
33+
file := os.NewFile(uintptr(newfd), "")
34+
if file == nil {
35+
return nil, errors.New("failed to open TUN file descriptor")
36+
}
37+
return file, nil
38+
}

intra/android/tun2socks.go

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,67 +15,96 @@
1515
package tun2socks
1616

1717
import (
18-
"runtime/debug"
18+
"errors"
19+
"io"
20+
"io/fs"
21+
"log"
22+
"os"
1923
"strings"
2024

2125
"github.com/Jigsaw-Code/outline-go-tun2socks/intra"
2226
"github.com/Jigsaw-Code/outline-go-tun2socks/intra/doh"
2327
"github.com/Jigsaw-Code/outline-go-tun2socks/intra/protect"
24-
"github.com/Jigsaw-Code/outline-go-tun2socks/tunnel"
25-
"github.com/eycorsican/go-tun2socks/common/log"
28+
"github.com/Jigsaw-Code/outline-sdk/network"
2629
)
2730

28-
func init() {
29-
// Conserve memory by increasing garbage collection frequency.
30-
debug.SetGCPercent(10)
31-
log.SetLevel(log.WARN)
32-
}
33-
3431
// ConnectIntraTunnel reads packets from a TUN device and applies the Intra routing
3532
// rules. Currently, this only consists of redirecting DNS packets to a specified
3633
// server; all other data flows directly to its destination.
3734
//
3835
// `fd` is the TUN device. The IntraTunnel acquires an additional reference to it, which
39-
// is released by IntraTunnel.Disconnect(), so the caller must close `fd` _and_ call
40-
// Disconnect() in order to close the TUN device.
36+
//
37+
// is released by IntraTunnel.Disconnect(), so the caller must close `fd` _and_ call
38+
// Disconnect() in order to close the TUN device.
39+
//
4140
// `fakedns` is the DNS server that the system believes it is using, in "host:port" style.
42-
// The port is normally 53.
41+
//
42+
// The port is normally 53.
43+
//
4344
// `udpdns` and `tcpdns` are the location of the actual DNS server being used. For DNS
44-
// tunneling in Intra, these are typically high-numbered ports on localhost.
45+
//
46+
// tunneling in Intra, these are typically high-numbered ports on localhost.
47+
//
4548
// `dohdns` is the initial DoH transport. It must not be `nil`.
4649
// `protector` is a wrapper for Android's VpnService.protect() method.
47-
// `listener` will be provided with a summary of each TCP and UDP socket when it is closed.
50+
// `eventListener` will be provided with a summary of each TCP and UDP socket when it is closed.
4851
//
4952
// Throws an exception if the TUN file descriptor cannot be opened, or if the tunnel fails to
5053
// connect.
51-
func ConnectIntraTunnel(fd int, fakedns string, dohdns doh.Transport, protector protect.Protector, listener intra.Listener) (intra.Tunnel, error) {
52-
tun, err := tunnel.MakeTunFile(fd)
54+
func ConnectIntraTunnel(
55+
fd int, fakedns string, dohdns doh.Transport, protector protect.Protector, eventListener intra.Listener,
56+
) (*intra.Tunnel, error) {
57+
tun, err := makeTunFile(fd)
5358
if err != nil {
5459
return nil, err
5560
}
56-
dialer := protect.MakeDialer(protector)
57-
config := protect.MakeListenConfig(protector)
58-
t, err := intra.NewTunnel(fakedns, dohdns, tun, dialer, config, listener)
61+
t, err := intra.NewTunnel(fakedns, dohdns, tun, protector, eventListener)
5962
if err != nil {
6063
return nil, err
6164
}
62-
go tunnel.ProcessInputPackets(t, tun)
65+
go copyUntilEOF(t, tun)
66+
go copyUntilEOF(tun, t)
6367
return t, nil
6468
}
6569

6670
// NewDoHTransport returns a DNSTransport that connects to the specified DoH server.
6771
// `url` is the URL of a DoH server (no template, POST-only). If it is nonempty, it
68-
// overrides `udpdns` and `tcpdns`.
72+
//
73+
// overrides `udpdns` and `tcpdns`.
74+
//
6975
// `ips` is an optional comma-separated list of IP addresses for the server. (This
70-
// wrapper is required because gomobile can't make bindings for []string.)
76+
//
77+
// wrapper is required because gomobile can't make bindings for []string.)
78+
//
7179
// `protector` is the socket protector to use for all external network activity.
7280
// `auth` will provide a client certificate if required by the TLS server.
73-
// `listener` will be notified after each DNS query succeeds or fails.
74-
func NewDoHTransport(url string, ips string, protector protect.Protector, auth doh.ClientAuth, listener intra.Listener) (doh.Transport, error) {
81+
// `eventListener` will be notified after each DNS query succeeds or fails.
82+
func NewDoHTransport(
83+
url string, ips string, protector protect.Protector, auth doh.ClientAuth, eventListener intra.Listener,
84+
) (doh.Transport, error) {
7585
split := []string{}
7686
if len(ips) > 0 {
7787
split = strings.Split(ips, ",")
7888
}
7989
dialer := protect.MakeDialer(protector)
80-
return doh.NewTransport(url, split, dialer, auth, listener)
90+
return doh.NewTransport(url, split, dialer, auth, eventListener)
91+
}
92+
93+
func copyUntilEOF(dst, src io.ReadWriteCloser) {
94+
log.Printf("[debug] start relaying traffic [%s] -> [%s]", src, dst)
95+
defer log.Printf("[debug] stop relaying traffic [%s] -> [%s]", src, dst)
96+
97+
const commonMTU = 1500
98+
buf := make([]byte, commonMTU)
99+
defer dst.Close()
100+
for {
101+
_, err := io.CopyBuffer(dst, src, buf)
102+
if err == nil || isErrClosed(err) {
103+
return
104+
}
105+
}
106+
}
107+
108+
func isErrClosed(err error) bool {
109+
return errors.Is(err, os.ErrClosed) || errors.Is(err, fs.ErrClosed) || errors.Is(err, network.ErrClosed)
81110
}

intra/ip.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2023 Jigsaw Operations LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package intra
16+
17+
import "net/netip"
18+
19+
// isEquivalentAddrPort checks if addr1 and addr2 are equivalent. More specifically, it will treat
20+
// "ffff::127.0.0.1" (IPv4-in-6) and "127.0.0.1" (IPv4) as equivalent, even though they are "!=" in Go.
21+
func isEquivalentAddrPort(addr1, addr2 netip.AddrPort) bool {
22+
return addr1.Addr().Unmap() == addr2.Addr().Unmap() && addr1.Port() == addr2.Port()
23+
}

intra/ip_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2023 Jigsaw Operations LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package intra
16+
17+
import (
18+
"net/netip"
19+
"testing"
20+
)
21+
22+
func TestIsEquivalentAddrPort(t *testing.T) {
23+
cases := []struct {
24+
in1, in2 netip.AddrPort
25+
want bool
26+
msg string
27+
}{
28+
{
29+
in1: netip.MustParseAddrPort("12.34.56.78:80"),
30+
in2: netip.AddrPortFrom(netip.AddrFrom4([4]byte{12, 34, 56, 78}), 80),
31+
want: true,
32+
},
33+
{
34+
in1: netip.MustParseAddrPort("[fe80::1234:5678]:443"),
35+
in2: netip.AddrPortFrom(netip.AddrFrom16([16]byte{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78}), 443),
36+
want: true,
37+
},
38+
{
39+
in1: netip.MustParseAddrPort("0.0.0.0:80"),
40+
in2: netip.MustParseAddrPort("127.0.0.1:80"),
41+
want: false,
42+
},
43+
{
44+
in1: netip.AddrPortFrom(netip.IPv6Unspecified(), 80),
45+
in2: netip.AddrPortFrom(netip.IPv6Loopback(), 80),
46+
want: false,
47+
},
48+
{
49+
in1: netip.MustParseAddrPort("127.0.0.1:38880"),
50+
in2: netip.MustParseAddrPort("127.0.0.1:38888"),
51+
want: false,
52+
},
53+
{
54+
in1: netip.MustParseAddrPort("[2001:db8:85a3:8d3:1319:8a2e:370:7348]:33443"),
55+
in2: netip.MustParseAddrPort("[2001:db8:85a3:8d3:1319:8a2e:370:7348]:33444"),
56+
want: false,
57+
},
58+
{
59+
in1: netip.MustParseAddrPort("127.0.0.1:8080"),
60+
in2: netip.MustParseAddrPort("[::ffff:127.0.0.1]:8080"),
61+
want: true,
62+
},
63+
{
64+
in1: netip.AddrPortFrom(netip.IPv6Loopback(), 80),
65+
in2: netip.MustParseAddrPort("127.0.0.1:80"),
66+
want: false,
67+
},
68+
}
69+
70+
for _, tc := range cases {
71+
actual := isEquivalentAddrPort(tc.in1, tc.in2)
72+
if actual != tc.want {
73+
t.Fatalf(`"%v" == "%v"? want %v, actual %v`, tc.in1, tc.in2, tc.want, actual)
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)