Skip to content

Commit 7830e4e

Browse files
committed
tun: add tun support (linux)
Signed-off-by: Mark Pashmfouroush <mark@markpash.me>
1 parent 69977d9 commit 7830e4e

File tree

7 files changed

+266
-97
lines changed

7 files changed

+266
-97
lines changed

app/app.go

Lines changed: 131 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
const singleMTU = 1330
1616
const doubleMTU = 1280 // minimum mtu for IPv6, may cause frag reassembly somewhere
17+
const connTestEndpoint = "http://1.1.1.1:80/"
1718

1819
type WarpOptions struct {
1920
Bind netip.AddrPort
@@ -22,12 +23,17 @@ type WarpOptions struct {
2223
Psiphon *PsiphonOptions
2324
Gool bool
2425
Scan *wiresocks.ScanOptions
26+
Tun *TunOptions
2527
}
2628

2729
type PsiphonOptions struct {
2830
Country string
2931
}
3032

33+
type TunOptions struct {
34+
FwMark uint32
35+
}
36+
3137
func RunWarp(ctx context.Context, l *slog.Logger, opts WarpOptions) error {
3238
if opts.Psiphon != nil && opts.Gool {
3339
return errors.New("can't use psiphon and gool at the same time")
@@ -37,6 +43,10 @@ func RunWarp(ctx context.Context, l *slog.Logger, opts WarpOptions) error {
3743
return errors.New("must provide country for psiphon")
3844
}
3945

46+
if opts.Psiphon != nil && opts.Tun != nil {
47+
return errors.New("can't use psiphon and tun at the same time")
48+
}
49+
4050
// create identities
4151
if err := createPrimaryAndSecondaryIdentities(l.With("subsystem", "warp/account"), opts.License); err != nil {
4252
return err
@@ -69,123 +79,211 @@ func RunWarp(ctx context.Context, l *slog.Logger, opts WarpOptions) error {
6979
case opts.Gool:
7080
l.Info("running in warp-in-warp (gool) mode")
7181
// run warp in warp
72-
warpErr = runWarpInWarp(ctx, l, opts.Bind, endpoints)
82+
warpErr = runWarpInWarp(ctx, l, opts.Bind, endpoints, opts.Tun)
7383
default:
7484
l.Info("running in normal warp mode")
7585
// just run primary warp on bindAddress
76-
warpErr = runWarp(ctx, l, opts.Bind, endpoints[0])
86+
warpErr = runWarp(ctx, l, opts.Bind, endpoints[0], opts.Tun)
7787
}
7888

7989
return warpErr
8090
}
8191

82-
func runWarp(ctx context.Context, l *slog.Logger, bind netip.AddrPort, endpoint string) error {
92+
func runWarp(ctx context.Context, l *slog.Logger, bind netip.AddrPort, endpoint string, tun *TunOptions) error {
93+
// Set up primary/outer warp config
8394
conf, err := wiresocks.ParseConfig("./stuff/primary/wgcf-profile.ini", endpoint)
8495
if err != nil {
8596
return err
8697
}
98+
99+
// Set up MTU
87100
conf.Interface.MTU = singleMTU
88101

102+
// Enable trick and keepalive on all peers in config
89103
for i, peer := range conf.Peers {
90104
peer.Trick = true
91105
peer.KeepAlive = 3
92106
conf.Peers[i] = peer
93107
}
94108

95-
tnet, err := wiresocks.StartWireguard(ctx, l, conf)
109+
if tun != nil {
110+
// Create a new tun interface
111+
tunDev, err := newNormalTun()
112+
if err != nil {
113+
return err
114+
}
115+
116+
// Establish wireguard tunnel on tun interface
117+
if err := establishWireguard(l, conf, tunDev, tun.FwMark); err != nil {
118+
return err
119+
}
120+
l.Info("serving tun", "interface", "warp0")
121+
return nil
122+
}
123+
124+
// Create userspace tun network stack
125+
tunDev, tnet, err := newUsermodeTun(conf)
96126
if err != nil {
97127
return err
98128
}
99129

100-
_, err = tnet.StartProxy(bind)
130+
// Establish wireguard on userspace stack
131+
if err := establishWireguard(l, conf, tunDev, 0); err != nil {
132+
return err
133+
}
134+
135+
// Test wireguard connectivity
136+
if err := usermodeTunTest(ctx, l, tnet); err != nil {
137+
return err
138+
}
139+
140+
// Run a proxy on the userspace stack
141+
_, err = wiresocks.StartProxy(ctx, l, tnet, bind)
101142
if err != nil {
102143
return err
103144
}
104145

105146
l.Info("serving proxy", "address", bind)
106-
107147
return nil
108148
}
109149

110-
func runWarpWithPsiphon(ctx context.Context, l *slog.Logger, bind netip.AddrPort, endpoint string, country string) error {
111-
conf, err := wiresocks.ParseConfig("./stuff/primary/wgcf-profile.ini", endpoint)
150+
func runWarpInWarp(ctx context.Context, l *slog.Logger, bind netip.AddrPort, endpoints []string, tun *TunOptions) error {
151+
// Set up primary/outer warp config
152+
conf, err := wiresocks.ParseConfig("./stuff/primary/wgcf-profile.ini", endpoints[0])
112153
if err != nil {
113154
return err
114155
}
156+
157+
// Set up MTU
115158
conf.Interface.MTU = singleMTU
116159

160+
// Enable trick and keepalive on all peers in config
117161
for i, peer := range conf.Peers {
118162
peer.Trick = true
119163
peer.KeepAlive = 3
120164
conf.Peers[i] = peer
121165
}
122166

123-
tnet, err := wiresocks.StartWireguard(ctx, l, conf)
167+
// Create userspace tun network stack
168+
tunDev, tnet, err := newUsermodeTun(conf)
124169
if err != nil {
125170
return err
126171
}
127172

128-
warpBind, err := tnet.StartProxy(netip.MustParseAddrPort("127.0.0.1:0"))
173+
// Establish wireguard on userspace stack
174+
if err := establishWireguard(l.With("gool", "outer"), conf, tunDev, 0); err != nil {
175+
return err
176+
}
177+
178+
// Test wireguard connectivity
179+
if err := usermodeTunTest(ctx, l, tnet); err != nil {
180+
return err
181+
}
182+
183+
// Create a UDP port forward between localhost and the remote endpoint
184+
addr, err := wiresocks.NewVtunUDPForwarder(ctx, netip.MustParseAddrPort("127.0.0.1:0"), endpoints[0], tnet, singleMTU)
129185
if err != nil {
130186
return err
131187
}
132188

133-
// run psiphon
134-
err = psiphon.RunPsiphon(ctx, l.With("subsystem", "psiphon"), warpBind.String(), bind.String(), country)
189+
// Set up secondary/inner warp config
190+
conf, err = wiresocks.ParseConfig("./stuff/secondary/wgcf-profile.ini", addr.String())
135191
if err != nil {
136-
return fmt.Errorf("unable to run psiphon %w", err)
192+
return err
137193
}
138194

139-
l.Info("serving proxy", "address", bind)
195+
// Set up MTU
196+
conf.Interface.MTU = doubleMTU
197+
198+
// Enable keepalive on all peers in config
199+
for i, peer := range conf.Peers {
200+
peer.KeepAlive = 10
201+
conf.Peers[i] = peer
202+
}
203+
204+
if tun != nil {
205+
// Create a new tun interface
206+
tunDev, err := newNormalTun()
207+
if err != nil {
208+
return err
209+
}
210+
211+
// Establish wireguard tunnel on tun interface
212+
if err := establishWireguard(l.With("gool", "inner"), conf, tunDev, tun.FwMark); err != nil {
213+
return err
214+
}
215+
l.Info("serving tun", "interface", "warp0")
216+
return nil
217+
}
218+
219+
// Create userspace tun network stack
220+
tunDev, tnet, err = newUsermodeTun(conf)
221+
if err != nil {
222+
return err
223+
}
224+
225+
// Establish wireguard on userspace stack
226+
if err := establishWireguard(l.With("gool", "inner"), conf, tunDev, 0); err != nil {
227+
return err
228+
}
229+
230+
// Test wireguard connectivity
231+
if err := usermodeTunTest(ctx, l, tnet); err != nil {
232+
return err
233+
}
234+
235+
_, err = wiresocks.StartProxy(ctx, l, tnet, bind)
236+
if err != nil {
237+
return err
238+
}
140239

240+
l.Info("serving proxy", "address", bind)
141241
return nil
142242
}
143243

144-
func runWarpInWarp(ctx context.Context, l *slog.Logger, bind netip.AddrPort, endpoints []string) error {
145-
// Run outer warp
146-
conf, err := wiresocks.ParseConfig("./stuff/primary/wgcf-profile.ini", endpoints[0])
244+
func runWarpWithPsiphon(ctx context.Context, l *slog.Logger, bind netip.AddrPort, endpoint string, country string) error {
245+
// Set up primary/outer warp config
246+
conf, err := wiresocks.ParseConfig("./stuff/primary/wgcf-profile.ini", endpoint)
147247
if err != nil {
148248
return err
149249
}
250+
251+
// Set up MTU
150252
conf.Interface.MTU = singleMTU
151253

254+
// Enable trick and keepalive on all peers in config
152255
for i, peer := range conf.Peers {
153256
peer.Trick = true
154257
peer.KeepAlive = 3
155258
conf.Peers[i] = peer
156259
}
157260

158-
tnet, err := wiresocks.StartWireguard(ctx, l.With("gool", "outer"), conf)
261+
// Create userspace tun network stack
262+
tunDev, tnet, err := newUsermodeTun(conf)
159263
if err != nil {
160264
return err
161265
}
162266

163-
// Create a UDP port forward between localhost and the remote endpoint
164-
addr, err := wiresocks.NewVtunUDPForwarder(ctx, netip.MustParseAddrPort("127.0.0.1:0"), endpoints[1], tnet, singleMTU)
165-
if err != nil {
267+
// Establish wireguard on userspace stack
268+
if err := establishWireguard(l, conf, tunDev, 0); err != nil {
166269
return err
167270
}
168271

169-
// Run inner warp
170-
conf, err = wiresocks.ParseConfig("./stuff/secondary/wgcf-profile.ini", addr.String())
171-
if err != nil {
272+
// Test wireguard connectivity
273+
if err := usermodeTunTest(ctx, l, tnet); err != nil {
172274
return err
173275
}
174-
conf.Interface.MTU = doubleMTU
175-
176-
for i, peer := range conf.Peers {
177-
peer.KeepAlive = 10
178-
conf.Peers[i] = peer
179-
}
180276

181-
tnet, err = wiresocks.StartWireguard(ctx, l.With("gool", "inner"), conf)
277+
// Run a proxy on the userspace stack
278+
warpBind, err := wiresocks.StartProxy(ctx, l, tnet, netip.MustParseAddrPort("127.0.0.1:0"))
182279
if err != nil {
183280
return err
184281
}
185282

186-
_, err = tnet.StartProxy(bind)
283+
// run psiphon
284+
err = psiphon.RunPsiphon(ctx, l.With("subsystem", "psiphon"), warpBind.String(), bind.String(), country)
187285
if err != nil {
188-
return err
286+
return fmt.Errorf("unable to run psiphon %w", err)
189287
}
190288

191289
l.Info("serving proxy", "address", bind)

app/wg.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package app
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"io"
8+
"log/slog"
9+
"net/http"
10+
"time"
11+
12+
"github.com/bepass-org/warp-plus/wireguard/conn"
13+
"github.com/bepass-org/warp-plus/wireguard/device"
14+
wgtun "github.com/bepass-org/warp-plus/wireguard/tun"
15+
"github.com/bepass-org/warp-plus/wireguard/tun/netstack"
16+
"github.com/bepass-org/warp-plus/wiresocks"
17+
)
18+
19+
func newNormalTun() (wgtun.Device, error) {
20+
tunDev, err := wgtun.CreateTUN("warp0", 1280)
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
return tunDev, nil
26+
}
27+
28+
func newUsermodeTun(conf *wiresocks.Configuration) (wgtun.Device, *netstack.Net, error) {
29+
tunDev, tnet, err := netstack.CreateNetTUN(conf.Interface.Addresses, conf.Interface.DNS, conf.Interface.MTU)
30+
if err != nil {
31+
return nil, nil, err
32+
}
33+
34+
return tunDev, tnet, nil
35+
}
36+
37+
func usermodeTunTest(ctx context.Context, l *slog.Logger, tnet *netstack.Net) error {
38+
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(30*time.Second))
39+
defer cancel()
40+
41+
for {
42+
select {
43+
case <-ctx.Done():
44+
return ctx.Err()
45+
default:
46+
}
47+
48+
client := http.Client{Transport: &http.Transport{
49+
DialContext: tnet.DialContext,
50+
ResponseHeaderTimeout: 5 * time.Second,
51+
}}
52+
resp, err := client.Get(connTestEndpoint)
53+
if err != nil {
54+
l.Error("connection test failed", "error", err.Error())
55+
continue
56+
}
57+
_, err = io.ReadAll(resp.Body)
58+
resp.Body.Close()
59+
if err != nil {
60+
l.Error("connection test failed", "error", err.Error())
61+
continue
62+
}
63+
64+
l.Info("connection test successful")
65+
break
66+
}
67+
68+
return nil
69+
}
70+
71+
func establishWireguard(l *slog.Logger, conf *wiresocks.Configuration, tunDev wgtun.Device, fwmark uint32) error {
72+
// create the IPC message to establish the wireguard conn
73+
var request bytes.Buffer
74+
75+
request.WriteString(fmt.Sprintf("private_key=%s\n", conf.Interface.PrivateKey))
76+
if fwmark != 0 {
77+
request.WriteString(fmt.Sprintf("fwmark=%d\n", fwmark))
78+
}
79+
80+
for _, peer := range conf.Peers {
81+
request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey))
82+
request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive))
83+
request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey))
84+
request.WriteString(fmt.Sprintf("endpoint=%s\n", peer.Endpoint))
85+
request.WriteString(fmt.Sprintf("trick=%t\n", peer.Trick))
86+
87+
for _, cidr := range peer.AllowedIPs {
88+
request.WriteString(fmt.Sprintf("allowed_ip=%s\n", cidr))
89+
}
90+
}
91+
92+
dev := device.NewDevice(
93+
tunDev,
94+
conn.NewDefaultBind(),
95+
device.NewSLogger(l.With("subsystem", "wireguard-go")),
96+
)
97+
98+
if err := dev.IpcSet(request.String()); err != nil {
99+
return err
100+
}
101+
102+
if err := dev.Up(); err != nil {
103+
return err
104+
}
105+
106+
return nil
107+
}

example_config.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@
77
"cfon": false,
88
"country": "DE",
99
"scan": true,
10-
"rtt": "1000ms"
10+
"rtt": "1000ms",
11+
"tun": false,
12+
"fwmark": "0x1375"
1113
}

0 commit comments

Comments
 (0)