From d59eee568e6bdb6bb935cb64fad4c6198cdb74ac Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Thu, 14 Mar 2024 10:12:03 +0000 Subject: [PATCH] wireguard: lots of fixes Migrate "trick" to wireguard UAPI IPC. Refactor config parsing. Add tests for config parsing. Allow for keepalive to be configurable. Set keepalive to 3s for outer tunnel and 10s for inner. Add logs for trick mode. Redo MTU settings (needs more testing). Signed-off-by: Mark Pashmfouroush --- app/app.go | 40 ++++--- device/device.go | 5 +- device/device_test.go | 2 +- device/noise_test.go | 2 +- device/peer.go | 1 - device/send.go | 2 + device/uapi.go | 9 ++ go.mod | 6 +- go.sum | 15 ++- wiresocks/config.go | 223 +++++++++++++-------------------------- wiresocks/config_test.go | 82 ++++++++++++++ wiresocks/wiresocks.go | 41 +++---- 12 files changed, 222 insertions(+), 206 deletions(-) create mode 100644 wiresocks/config_test.go diff --git a/app/app.go b/app/app.go index 6e001d087..14c082242 100644 --- a/app/app.go +++ b/app/app.go @@ -16,6 +16,9 @@ import ( "github.com/bepass-org/wireguard-go/wiresocks" ) +const singleMTU = 1400 +const doubleMTU = 1320 + type WarpOptions struct { LogLevel string Bind netip.AddrPort @@ -88,34 +91,41 @@ func RunWarp(ctx context.Context, opts WarpOptions) error { warpErr = runWarpInWarp(ctx, opts.Bind, endpoints, opts.LogLevel == "debug") default: // just run primary warp on bindAddress - _, _, warpErr = runWarp(ctx, opts.Bind, endpoints, "./primary/wgcf-profile.ini", opts.LogLevel == "debug", true, true) + _, warpErr = runWarp(ctx, opts.Bind, endpoints, "./primary/wgcf-profile.ini", opts.LogLevel == "debug", true, true, singleMTU) } return warpErr } -func runWarp(ctx context.Context, bind netip.AddrPort, endpoints []string, confPath string, verbose, startProxy bool, trick bool) (*wiresocks.VirtualTun, int, error) { +func runWarp(ctx context.Context, bind netip.AddrPort, endpoints []string, confPath string, verbose, startProxy bool, trick bool, mtu int) (*wiresocks.VirtualTun, error) { conf, err := wiresocks.ParseConfig(confPath, endpoints[0]) if err != nil { log.Println(err) - return nil, 0, err + return nil, err } + conf.Interface.MTU = mtu + + for i, peer := range conf.Peers { + peer.KeepAlive = 10 + if trick { + peer.Trick = true + peer.KeepAlive = 3 + } - if trick { - conf.Device.Trick = trick + conf.Peers[i] = peer } - tnet, err := wiresocks.StartWireguard(ctx, conf.Device, verbose) + tnet, err := wiresocks.StartWireguard(ctx, conf, verbose) if err != nil { log.Println(err) - return nil, 0, err + return nil, err } if startProxy { tnet.StartProxy(bind) } - return tnet, conf.Device.MTU, nil + return tnet, nil } func runWarpWithPsiphon(ctx context.Context, bind netip.AddrPort, endpoints []string, country string, verbose bool) error { @@ -126,7 +136,7 @@ func runWarpWithPsiphon(ctx context.Context, bind netip.AddrPort, endpoints []st return err } - _, _, err = runWarp(ctx, warpBindAddress, endpoints, "./primary/wgcf-profile.ini", verbose, true, true) + _, err = runWarp(ctx, warpBindAddress, endpoints, "./primary/wgcf-profile.ini", verbose, true, true, singleMTU) if err != nil { return err } @@ -144,27 +154,27 @@ func runWarpWithPsiphon(ctx context.Context, bind netip.AddrPort, endpoints []st } func runWarpInWarp(ctx context.Context, bind netip.AddrPort, endpoints []string, verbose bool) error { - // run secondary warp - vTUN, mtu, err := runWarp(ctx, netip.AddrPort{}, endpoints, "./secondary/wgcf-profile.ini", verbose, false, true) + // Run outer warp + vTUN, err := runWarp(ctx, netip.AddrPort{}, endpoints, "./secondary/wgcf-profile.ini", verbose, false, true, singleMTU) if err != nil { return err } - // run virtual endpoint + // Run virtual endpoint virtualEndpointBindAddress, err := findFreePort("udp") if err != nil { log.Println("There are no free udp ports on Device!") return err } addr := endpoints[1] - err = wiresocks.NewVtunUDPForwarder(virtualEndpointBindAddress.String(), addr, vTUN, mtu+100, ctx) + err = wiresocks.NewVtunUDPForwarder(virtualEndpointBindAddress.String(), addr, vTUN, singleMTU, ctx) if err != nil { log.Println(err) return err } - // run primary warp - _, _, err = runWarp(ctx, bind, []string{virtualEndpointBindAddress.String()}, "./primary/wgcf-profile.ini", verbose, true, false) + // Run inner warp + _, err = runWarp(ctx, bind, []string{virtualEndpointBindAddress.String()}, "./primary/wgcf-profile.ini", verbose, true, false, doubleMTU) if err != nil { return err } diff --git a/device/device.go b/device/device.go index 526ed2da5..d3568ed1c 100644 --- a/device/device.go +++ b/device/device.go @@ -86,8 +86,6 @@ type Device struct { mtu atomic.Int32 } - trick bool - ipcMutex sync.RWMutex closed chan struct{} log *Logger @@ -283,9 +281,8 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error { return nil } -func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger, trick bool) *Device { +func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device { device := new(Device) - device.trick = trick device.state.state.Store(uint32(deviceStateDown)) device.closed = make(chan struct{}) device.log = logger diff --git a/device/device_test.go b/device/device_test.go index 8aa310b03..8b170a266 100644 --- a/device/device_test.go +++ b/device/device_test.go @@ -166,7 +166,7 @@ func genTestPair(tb testing.TB, realSocket bool) (pair testPair) { if _, ok := tb.(*testing.B); ok && !testing.Verbose() { level = LogLevelError } - p.dev = NewDevice(p.tun.TUN(), binds[i], NewLogger(level, fmt.Sprintf("dev%d: ", i)), false) + p.dev = NewDevice(p.tun.TUN(), binds[i], NewLogger(level, fmt.Sprintf("dev%d: ", i))) if err := p.dev.IpcSet(cfg[i]); err != nil { tb.Errorf("failed to configure device %d: %v", i, err) p.dev.Close() diff --git a/device/noise_test.go b/device/noise_test.go index 83c3420af..d6aae0851 100644 --- a/device/noise_test.go +++ b/device/noise_test.go @@ -39,7 +39,7 @@ func randDevice(t *testing.T) *Device { } tun := tuntest.NewChannelTUN() logger := NewLogger(LogLevelError, "") - device := NewDevice(tun.TUN(), conn.NewDefaultBind(), logger, false) + device := NewDevice(tun.TUN(), conn.NewDefaultBind(), logger) device.SetPrivateKey(sk) return device } diff --git a/device/peer.go b/device/peer.go index 3b84c02e2..616efdf7a 100644 --- a/device/peer.go +++ b/device/peer.go @@ -81,7 +81,6 @@ func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) { // create peer peer := new(Peer) peer.stopCh = make(chan int, 1) - peer.trick = true peer.cookieGenerator.Init(pk) peer.device = device peer.queue.outbound = newAutodrainingOutboundQueue(device) diff --git a/device/send.go b/device/send.go index 05479419d..f135b8242 100644 --- a/device/send.go +++ b/device/send.go @@ -121,6 +121,7 @@ func (peer *Peer) SendKeepalive() { if len(peer.queue.staged) == 0 && peer.isRunning.Load() { // Send some random packets on every keepalive if peer.trick { + peer.device.log.Verbosef("%v - Running tricks! (keepalive)", peer) peer.sendRandomPackets() } @@ -159,6 +160,7 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error { // send some random packets on handshake if peer.trick { + peer.device.log.Verbosef("%v - Running tricks! (handshake)", peer) peer.sendRandomPackets() } diff --git a/device/uapi.go b/device/uapi.go index 0a4ca0ade..108506fb8 100644 --- a/device/uapi.go +++ b/device/uapi.go @@ -119,6 +119,7 @@ func (device *Device) IpcGetOperation(w io.Writer) error { sendf("tx_bytes=%d", peer.txBytes.Load()) sendf("rx_bytes=%d", peer.rxBytes.Load()) sendf("persistent_keepalive_interval=%d", peer.persistentKeepaliveInterval.Load()) + sendf("trick=%t", peer.trick) device.allowedips.EntriesForPeer(peer, func(prefix netip.Prefix) bool { sendf("allowed_ip=%s", prefix.String()) @@ -386,6 +387,14 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error return ipcErrorf(ipc.IpcErrorInvalid, "invalid protocol version: %v", value) } + case "trick": + device.log.Verbosef("%v - UAPI: Setting trick: %s", peer.Peer, value) + parsedBool, err := strconv.ParseBool(value) + if err != nil { + return ipcErrorf(ipc.IpcErrorInvalid, "invalid trick value: %v", value) + } + peer.trick = parsedBool + default: return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI peer key: %v", key) } diff --git a/go.mod b/go.mod index ab91e1b1a..6a9193b0c 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,13 @@ go 1.21.1 replace github.com/Psiphon-Labs/psiphon-tunnel-core => github.com/bepass-org/psiphon-tunnel-core v0.0.0-20240311155012-9c2e10df08e5 require ( - github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/Psiphon-Labs/psiphon-tunnel-core v2.0.28+incompatible github.com/bepass-org/proxy v0.0.0-20240201095508-c86216dd0aea github.com/fatih/color v1.16.0 github.com/flynn/noise v1.1.0 + github.com/frankban/quicktest v1.14.6 github.com/go-ini/ini v1.67.0 + github.com/google/go-cmp v0.6.0 github.com/hashicorp/golang-lru v1.0.2 github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 github.com/quic-go/quic-go v0.40.1 @@ -48,6 +49,8 @@ require ( github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 // indirect github.com/juju/ratelimit v1.0.2 // indirect github.com/klauspost/compress v1.16.7 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -67,6 +70,7 @@ require ( github.com/refraction-networking/ed25519 v0.1.2 // indirect github.com/refraction-networking/gotapdance v1.7.10 // indirect github.com/refraction-networking/obfs4 v0.1.2 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect diff --git a/go.sum b/go.sum index 5769cd374..9fac1a6b6 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 h1:CVuXDbdzPW github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= -github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4= github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag= @@ -37,6 +35,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea h1:9C2rdYRp8Vzwhm3sbFX0yYfB+70zKFRjn7cnPCucHSw= github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea/go.mod h1:MdyNkAe06D7xmJsf+MsLvbZKYNXuOHLKJrvw+x4LlcQ= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,6 +59,8 @@ github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0 h1:7ZJyJV4Ki github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0/go.mod h1:2z3Tfqwv2ueuK6h563xUHRcCh1mv38wS9EjiWiesk84= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= @@ -80,6 +81,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= @@ -98,11 +100,13 @@ github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSg github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a h1:6SRny9FLB1eWasPyDUqBQnMi9NhXU01XIlB0ao89YoI= @@ -153,6 +157,7 @@ github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/ github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -177,6 +182,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rodaine/table v1.1.1 h1:zBliy3b4Oj6JRmncse2Z85WmoQvDrXOYuy0JXCt8Qz8= github.com/rodaine/table v1.1.1/go.mod h1:iqTRptjn+EVcrVBYtNMlJ2wrJZa3MpULUmcXFpfcziA= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 h1:ML7ZNtcln5UBo5Wv7RIv9Xg3Pr5VuRCWLFXEwda54Y4= diff --git a/wiresocks/config.go b/wiresocks/config.go index 378d3e522..0ddff54d8 100644 --- a/wiresocks/config.go +++ b/wiresocks/config.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/netip" - "strings" "github.com/go-ini/ini" ) @@ -14,50 +13,22 @@ import ( type PeerConfig struct { PublicKey string PreSharedKey string - Endpoint *string + Endpoint string KeepAlive int AllowedIPs []netip.Prefix + Trick bool } -// DeviceConfig contains the information to initiate a wireguard connection -type DeviceConfig struct { - SecretKey string - Endpoint []netip.Addr - Peers []PeerConfig +type InterfaceConfig struct { + PrivateKey string + Addresses []netip.Addr DNS []netip.Addr MTU int - ListenPort *int - Trick bool } type Configuration struct { - Device *DeviceConfig -} - -var ( - dnsAddresses = []string{"8.8.8.8", "8.8.4.4"} - dc = 0 -) - -func parseString(section *ini.Section, keyName string) (string, error) { - key := section.Key(strings.ToLower(keyName)) - if key == nil { - return "", fmt.Errorf("%s should not be empty", keyName) - } - return key.String(), nil -} - -func parseBase64KeyToHex(section *ini.Section, keyName string) (string, error) { - key, err := parseString(section, keyName) - if err != nil { - return "", err - } - result, err := encodeBase64ToHex(key) - if err != nil { - return result, err - } - - return result, nil + Interface *InterfaceConfig + Peers []PeerConfig } func encodeBase64ToHex(key string) (string, error) { @@ -71,140 +42,94 @@ func encodeBase64ToHex(key string) (string, error) { return hex.EncodeToString(decoded), nil } -func parseNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) { - key := section.Key(keyName) - if key == nil { - return []netip.Addr{}, nil - } - - var ips []netip.Addr - for _, str := range key.StringsWithShadows(",") { - str = strings.TrimSpace(str) - if str == "1.1.1.1" { - str = dnsAddresses[dc%len(dnsAddresses)] - dc++ - } - ip, err := netip.ParseAddr(str) - if err != nil { - return nil, err - } - ips = append(ips, ip) +// ParseInterface parses the [Interface] section +func ParseInterface(cfg *ini.File) (InterfaceConfig, error) { + device := InterfaceConfig{} + interfaces, err := cfg.SectionsByName("Interface") + if len(interfaces) != 1 || err != nil { + return InterfaceConfig{}, errors.New("only one [Interface] is expected") } - return ips, nil -} + iface := interfaces[0] -func parseCIDRNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) { - key := section.Key(keyName) + key := iface.Key("Address") if key == nil { - return []netip.Addr{}, nil + return InterfaceConfig{}, nil } - var ips []netip.Addr + var addresses []netip.Addr for _, str := range key.StringsWithShadows(",") { prefix, err := netip.ParsePrefix(str) if err != nil { - return nil, err + return InterfaceConfig{}, err } - addr := prefix.Addr() - ips = append(ips, addr) + addresses = append(addresses, prefix.Addr()) } - return ips, nil -} + device.Addresses = addresses -func parseAllowedIPs(section *ini.Section) ([]netip.Prefix, error) { - key := section.Key("AllowedIPs") + key = iface.Key("PrivateKey") if key == nil { - return []netip.Prefix{}, nil - } - - var ips []netip.Prefix - for _, str := range key.StringsWithShadows(",") { - prefix, err := netip.ParsePrefix(str) - if err != nil { - return nil, err - } - - ips = append(ips, prefix) - } - return ips, nil -} - -// ParseInterface parses the [Interface] section and extract the information into `device` -func ParseInterface(cfg *ini.File, device *DeviceConfig) error { - sections, err := cfg.SectionsByName("Interface") - if len(sections) != 1 || err != nil { - return errors.New("one and only one [Interface] is expected") - } - section := sections[0] - - address, err := parseCIDRNetIP(section, "Address") - if err != nil { - return err + return InterfaceConfig{}, errors.New("PrivateKey should not be empty") } - device.Endpoint = address - - privKey, err := parseBase64KeyToHex(section, "PrivateKey") + privateKeyHex, err := encodeBase64ToHex(key.String()) if err != nil { - return err + return InterfaceConfig{}, err } - device.SecretKey = privKey + device.PrivateKey = privateKeyHex - dns, err := parseNetIP(section, "DNS") - if err != nil { - return err + key = iface.Key("DNS") + if key == nil { + return InterfaceConfig{}, nil } - device.DNS = dns - if sectionKey, err := section.GetKey("MTU"); err == nil { - value, err := sectionKey.Int() + addresses = []netip.Addr{} + for _, str := range key.StringsWithShadows(",") { + ip, err := netip.ParseAddr(str) if err != nil { - return err - } - device.MTU = value - } else { - if dc == 0 { - device.MTU = 1420 - } else { - device.MTU = 1300 + return InterfaceConfig{}, err } + addresses = append(addresses, ip) } + device.DNS = addresses - if sectionKey, err := section.GetKey("ListenPort"); err == nil { + if sectionKey, err := iface.GetKey("MTU"); err == nil { value, err := sectionKey.Int() if err != nil { - return err + return InterfaceConfig{}, err } - device.ListenPort = &value + device.MTU = value } - return nil + return device, nil } // ParsePeers parses the [Peer] section and extract the information into `peers` -func ParsePeers(cfg *ini.File, peers *[]PeerConfig, endpoint string) error { +func ParsePeers(cfg *ini.File) ([]PeerConfig, error) { sections, err := cfg.SectionsByName("Peer") if len(sections) < 1 || err != nil { - return errors.New("at least one [Peer] is expected") + return nil, errors.New("at least one [Peer] is expected") } - for _, section := range sections { + peers := make([]PeerConfig, len(sections)) + for i, section := range sections { peer := PeerConfig{ PreSharedKey: "0000000000000000000000000000000000000000000000000000000000000000", KeepAlive: 0, } - decoded, err := parseBase64KeyToHex(section, "PublicKey") - if err != nil { - return err + if sectionKey, err := section.GetKey("PublicKey"); err == nil { + value, err := encodeBase64ToHex(sectionKey.String()) + if err != nil { + return nil, err + } + peer.PublicKey = value } - peer.PublicKey = decoded if sectionKey, err := section.GetKey("PreSharedKey"); err == nil { value, err := encodeBase64ToHex(sectionKey.String()) if err != nil { - return err + return nil, err } peer.PreSharedKey = value } @@ -212,21 +137,31 @@ func ParsePeers(cfg *ini.File, peers *[]PeerConfig, endpoint string) error { if sectionKey, err := section.GetKey("PersistentKeepalive"); err == nil { value, err := sectionKey.Int() if err != nil { - return err + return nil, err } peer.KeepAlive = value } - peer.AllowedIPs, err = parseAllowedIPs(section) - if err != nil { - return err + if sectionKey, err := section.GetKey("AllowedIPs"); err == nil { + var ips []netip.Prefix + for _, str := range sectionKey.StringsWithShadows(",") { + prefix, err := netip.ParsePrefix(str) + if err != nil { + return nil, err + } + ips = append(ips, prefix) + } + peer.AllowedIPs = ips } - peer.Endpoint = &endpoint + if sectionKey, err := section.GetKey("Endpoint"); err == nil { + peer.Endpoint = sectionKey.String() + } - *peers = append(*peers, peer) + peers[i] = peer } - return nil + + return peers, nil } // ParseConfig takes the path of a configuration file and parses it into Configuration @@ -242,31 +177,19 @@ func ParseConfig(path string, endpoint string) (*Configuration, error) { return nil, err } - device := &DeviceConfig{ - MTU: 1420, - } - - root := cfg.Section("") - wgConf, err := root.GetKey("WGConfig") - wgCfg := cfg - if err == nil { - wgCfg, err = ini.LoadSources(iniOpt, wgConf.String()) - if err != nil { - return nil, err - } - } - - err = ParseInterface(wgCfg, device) + iface, err := ParseInterface(cfg) if err != nil { return nil, err } - err = ParsePeers(wgCfg, &device.Peers, endpoint) + peers, err := ParsePeers(cfg) if err != nil { return nil, err } + for i, peer := range peers { + peer.Endpoint = endpoint + peers[i] = peer + } - return &Configuration{ - Device: device, - }, nil + return &Configuration{Interface: &iface, Peers: peers}, nil } diff --git a/wiresocks/config_test.go b/wiresocks/config_test.go new file mode 100644 index 000000000..3b4d62c54 --- /dev/null +++ b/wiresocks/config_test.go @@ -0,0 +1,82 @@ +package wiresocks + +import ( + "net/netip" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/go-ini/ini" + "github.com/google/go-cmp/cmp/cmpopts" +) + +const testConfig = ` +[Interface] +PrivateKey = aK8FWhiV1CtKFbKUPssL13P+Tv+c5owmYcU5PCP6yFw= +DNS = 8.8.8.8 +Address = 172.16.0.2/24 +Address = 2606:4700:110:8cc0:1ad3:9155:6742:ea8d/128 +[Peer] +PublicKey = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo= +AllowedIPs = 0.0.0.0/0 +AllowedIPs = ::/0 +Endpoint = engage.cloudflareclient.com:2408 +` +const ( + privateKeyBase64 = "68af055a1895d42b4a15b2943ecb0bd773fe4eff9ce68c2661c5393c23fac85c" + publicKeyBase64 = "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a" + presharedKeyBase64 = "0000000000000000000000000000000000000000000000000000000000000000" +) + +func TestParseInterface(t *testing.T) { + opts := ini.LoadOptions{ + Insensitive: true, + AllowShadows: true, + AllowNonUniqueSections: true, + } + + cfg, err := ini.LoadSources(opts, []byte(testConfig)) + qt.Assert(t, err, qt.IsNil) + + device, err := ParseInterface(cfg) + qt.Assert(t, err, qt.IsNil) + + want := InterfaceConfig{ + PrivateKey: privateKeyBase64, + Addresses: []netip.Addr{ + netip.MustParseAddr("172.16.0.2"), + netip.MustParseAddr("2606:4700:110:8cc0:1ad3:9155:6742:ea8d"), + }, + DNS: []netip.Addr{netip.MustParseAddr("8.8.8.8")}, + MTU: 0, + } + qt.Assert(t, device, qt.CmpEquals(cmpopts.EquateComparable(netip.Addr{})), want) + t.Logf("%+v", device) +} + +func TestParsePeers(t *testing.T) { + opts := ini.LoadOptions{ + Insensitive: true, + AllowShadows: true, + AllowNonUniqueSections: true, + } + + cfg, err := ini.LoadSources(opts, []byte(testConfig)) + qt.Assert(t, err, qt.IsNil) + + peers, err := ParsePeers(cfg) + qt.Assert(t, err, qt.IsNil) + + want := []PeerConfig{{ + PublicKey: publicKeyBase64, + PreSharedKey: presharedKeyBase64, + Endpoint: "engage.cloudflareclient.com:2408", + KeepAlive: 0, + AllowedIPs: []netip.Prefix{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + Trick: false, + }} + qt.Assert(t, peers, qt.CmpEquals(cmpopts.EquateComparable(netip.Prefix{})), want) + t.Logf("%+v", peers) +} diff --git a/wiresocks/wiresocks.go b/wiresocks/wiresocks.go index 2acddee6e..c8898d337 100644 --- a/wiresocks/wiresocks.go +++ b/wiresocks/wiresocks.go @@ -6,7 +6,6 @@ import ( "fmt" "net/netip" - "github.com/MakeNowJust/heredoc/v2" "github.com/bepass-org/wireguard-go/conn" "github.com/bepass-org/wireguard-go/device" "github.com/bepass-org/wireguard-go/tun/netstack" @@ -21,45 +20,29 @@ type DeviceSetting struct { } // serialize the config into an IPC request and DeviceSetting -func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) { +func createIPCRequest(conf *Configuration) (*DeviceSetting, error) { var request bytes.Buffer - request.WriteString(fmt.Sprintf("private_key=%s\n", conf.SecretKey)) - - if conf.ListenPort != nil { - request.WriteString(fmt.Sprintf("listen_port=%d\n", *conf.ListenPort)) - } + request.WriteString(fmt.Sprintf("private_key=%s\n", conf.Interface.PrivateKey)) for _, peer := range conf.Peers { - request.WriteString(fmt.Sprintf(heredoc.Doc(` - public_key=%s - persistent_keepalive_interval=%d - preshared_key=%s - `), - peer.PublicKey, 1, peer.PreSharedKey, - )) - if peer.Endpoint != nil { - request.WriteString(fmt.Sprintf("endpoint=%s\n", *peer.Endpoint)) - } + request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey)) + request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive)) + request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey)) + request.WriteString(fmt.Sprintf("endpoint=%s\n", peer.Endpoint)) + request.WriteString(fmt.Sprintf("trick=%t\n", peer.Trick)) - if len(peer.AllowedIPs) > 0 { - for _, ip := range peer.AllowedIPs { - request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip.String())) - } - } else { - request.WriteString(heredoc.Doc(` - allowed_ip=0.0.0.0/0 - allowed_ip=::0/0 - `)) + for _, cidr := range peer.AllowedIPs { + request.WriteString(fmt.Sprintf("allowed_ip=%s\n", cidr)) } } - setting := &DeviceSetting{ipcRequest: request.String(), dns: conf.DNS, deviceAddr: conf.Endpoint, mtu: conf.MTU} + setting := &DeviceSetting{ipcRequest: request.String(), dns: conf.Interface.DNS, deviceAddr: conf.Interface.Addresses, mtu: conf.Interface.MTU} return setting, nil } // StartWireguard creates a tun interface on netstack given a configuration -func StartWireguard(ctx context.Context, conf *DeviceConfig, verbose bool) (*VirtualTun, error) { +func StartWireguard(ctx context.Context, conf *Configuration, verbose bool) (*VirtualTun, error) { setting, err := createIPCRequest(conf) if err != nil { return nil, err @@ -75,7 +58,7 @@ func StartWireguard(ctx context.Context, conf *DeviceConfig, verbose bool) (*Vir logLevel = device.LogLevelSilent } - dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(logLevel, ""), conf.Trick) + dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(logLevel, "")) err = dev.IpcSet(setting.ipcRequest) if err != nil { return nil, err