diff --git a/.github/workflows/go-build.yaml b/.github/workflows/go-build.yaml index b4542e48d..0f50c53c2 100644 --- a/.github/workflows/go-build.yaml +++ b/.github/workflows/go-build.yaml @@ -60,7 +60,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' check-latest: true - name: Build warp diff --git a/app/app.go b/app/app.go index f62913f71..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 @@ -43,7 +46,7 @@ func RunWarp(ctx context.Context, opts WarpOptions) error { return errors.New("must provide country for psiphon") } - //create necessary file structures + // create necessary file structures if err := makeDirs(); err != nil { return err } @@ -55,21 +58,28 @@ func RunWarp(ctx context.Context, opts WarpOptions) error { } log.Println("Changed working directory to 'stuff'") - //create identities + // create identities if err := createPrimaryAndSecondaryIdentities(opts.License); err != nil { return err } - //Decide Working Scenario + // Decide Working Scenario endpoints := []string{opts.Endpoint, opts.Endpoint} if opts.Scan != nil { - var err error - endpoints, err = wiresocks.RunScan(ctx, opts.Scan.MaxRTT) + res, err := wiresocks.RunScan(ctx, opts.Scan.MaxRTT) if err != nil { return err } + + log.Printf("scan results: %+v", res) + + endpoints = make([]string, len(res)) + for i := 0; i < len(res); i++ { + endpoints[i] = res[i].AddrPort.String() + } } + log.Printf("using warp endpoints: %+v", endpoints) var warpErr error switch { @@ -81,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 - if trick { - conf.Device.Trick = trick + for i, peer := range conf.Peers { + peer.KeepAlive = 10 + if trick { + peer.Trick = true + peer.KeepAlive = 3 + } + + 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 { @@ -119,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 } @@ -128,7 +145,7 @@ func runWarpWithPsiphon(ctx context.Context, bind netip.AddrPort, endpoints []st err = psiphon.RunPsiphon(warpBindAddress.String(), bind.String(), country, ctx) if err != nil { log.Printf("unable to run psiphon %v", err) - return fmt.Errorf("unable to run psiphon %v", err) + return fmt.Errorf("unable to run psiphon %w", err) } log.Printf("Serving on %s", bind) @@ -137,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 } @@ -197,7 +214,7 @@ func createPrimaryAndSecondaryIdentities(license string) error { err := warp.LoadOrCreateIdentity(license) if err != nil { log.Printf("error: %v", err) - return fmt.Errorf("error: %v", err) + return err } } // make secondary @@ -206,7 +223,7 @@ func createPrimaryAndSecondaryIdentities(license string) error { err := warp.LoadOrCreateIdentity(license) if err != nil { log.Printf("error: %v", err) - return fmt.Errorf("error: %v", err) + return err } } return nil @@ -219,7 +236,7 @@ func makeDirs() error { // Check if 'stuff' directory exists, if not create it if _, err := os.Stat(stuffDir); os.IsNotExist(err) { - if err := os.Mkdir(stuffDir, 0755); err != nil { + if err := os.Mkdir(stuffDir, 0o755); err != nil { return fmt.Errorf("error creating 'stuff' directory: %w", err) } } @@ -227,7 +244,7 @@ func makeDirs() error { // Create 'primary' and 'secondary' directories if they don't exist for _, dir := range []string{primaryDir, secondaryDir} { if _, err := os.Stat(filepath.Join(stuffDir, dir)); os.IsNotExist(err) { - if err := os.Mkdir(filepath.Join(stuffDir, dir), 0755); err != nil { + if err := os.Mkdir(filepath.Join(stuffDir, dir), 0o755); err != nil { return fmt.Errorf("error creating '%s' directory: %w", dir, 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 535bfc02b..6a9193b0c 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,26 @@ module github.com/bepass-org/wireguard-go -go 1.20 +go 1.21.1 -replace github.com/Psiphon-Labs/psiphon-tunnel-core => github.com/bepass-org/psiphon-tunnel-core v0.0.0-20240223214330-9783d71283bc +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 v0.0.0-00010101000000-000000000000 - github.com/bepass-org/ipscanner v0.0.0-20240205155121-8927b7437d16 + 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 github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 github.com/refraction-networking/utls v1.3.3 - golang.org/x/crypto v0.18.0 - golang.org/x/net v0.20.0 - golang.org/x/sys v0.16.0 + github.com/rodaine/table v1.1.1 + golang.org/x/crypto v0.19.0 + golang.org/x/net v0.21.0 + golang.org/x/sys v0.17.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 ) @@ -26,30 +31,29 @@ require ( github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 // indirect github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 // indirect github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 // indirect - github.com/Psiphon-Labs/qtls-go1-19 v0.0.0-20230608213623-d58aa73e519a // indirect - github.com/Psiphon-Labs/qtls-go1-20 v0.0.0-20230608214729-dd57d6787acf // indirect - github.com/Psiphon-Labs/quic-go v0.0.0-20230626192210-73f29effc9da // indirect - github.com/Psiphon-Labs/tls-tris v0.0.0-20230824155421-58bf6d336a9a // indirect + github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240305020009-09f917290799 // indirect + github.com/Psiphon-Labs/quic-go v0.0.0-20240305203241-7c4a760d03cc // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f // indirect github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61 // indirect github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 // indirect github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/siphash v1.2.3 // indirect github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273 // indirect github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect - github.com/flynn/noise v1.1.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 // indirect - github.com/hashicorp/golang-lru v1.0.2 // 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 github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300 // indirect github.com/mroth/weightedrand v1.0.0 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect @@ -63,10 +67,10 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect - github.com/quic-go/quic-go v0.40.1 // indirect 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 f49965b1a..9fac1a6b6 100644 --- a/go.sum +++ b/go.sum @@ -5,34 +5,29 @@ filippo.io/keygen v0.0.0-20230306160926-5201437acf8e/go.mod h1:ZGSiF/b2hd6MRghF/ github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 h1:CVuXDbdzPW0XCNYTldy5dQues57geAs+vfwz3FTTpy8= 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/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/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 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= github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7/go.mod h1:alTtZBo3j4AWFvUrAH6F5ZaHcTj4G5Y01nHz8dkU6vU= github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 h1:VmnMMMheFXwLV0noxYhbJbLmkV4iaVW3xNnj6xcCNHo= github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464/go.mod h1:Pe5BqN2DdIdChorAXl6bDaQd/wghpCleJfid2NoSli0= -github.com/Psiphon-Labs/qtls-go1-19 v0.0.0-20230608213623-d58aa73e519a h1:O8D+GcEoZwutcERaABP2AM3RDvswBVtNmBWvlBn5wiw= -github.com/Psiphon-Labs/qtls-go1-19 v0.0.0-20230608213623-d58aa73e519a/go.mod h1:81bbD3bvEvi3BSamZb30PgvPvqwSLfEPqwwmq5sx7fc= -github.com/Psiphon-Labs/qtls-go1-20 v0.0.0-20230608214729-dd57d6787acf h1:bGS+WxWdHHuf42hn3M1GFSJbzCgtKNVTuiRqwCo3zyc= -github.com/Psiphon-Labs/qtls-go1-20 v0.0.0-20230608214729-dd57d6787acf/go.mod h1:wUiSd0qyefymNlikc99B2rRC01YPN1uUvDMytMOGmF8= -github.com/Psiphon-Labs/quic-go v0.0.0-20230626192210-73f29effc9da h1:TI2+ExyFR3A0kPrFHfaM6y3RybP0HGfP9N1R8hfZzfk= -github.com/Psiphon-Labs/quic-go v0.0.0-20230626192210-73f29effc9da/go.mod h1:wTIxqsKVrEQIxVIIYOEHuscY+PM3h6Wz79u5aF60fo0= -github.com/Psiphon-Labs/tls-tris v0.0.0-20230824155421-58bf6d336a9a h1:BOfU6ghaMsT/c40sWHmf3PXNwIendYXzL6tRv6NbPog= -github.com/Psiphon-Labs/tls-tris v0.0.0-20230824155421-58bf6d336a9a/go.mod h1:v3y9GXFo9Sf2mO6auD2ExGG7oDgrK8TI7eb49ZnUxrE= +github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240305020009-09f917290799 h1:dHFQz6jeIr2RdtlioyGIdJw2UfKF7G+g7GYnQxhbgrk= +github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240305020009-09f917290799/go.mod h1:ECTyVpleBW9oR/iHi185js4Fs7YD5T8A6tujOUzltxs= +github.com/Psiphon-Labs/quic-go v0.0.0-20240305203241-7c4a760d03cc h1:o9jpHz1Vuum0oasqBX4kKB8VQrR+VJzEJsBg6XAz5YU= +github.com/Psiphon-Labs/quic-go v0.0.0-20240305203241-7c4a760d03cc/go.mod h1:1gvBCJ18gsMqvZXkPkq0u9/BQKvjNS5RFWwF5uLl2Ys= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f h1:SaJ6yqg936TshyeFZqQE+N+9hYkIeL9AMr7S4voCl10= github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= -github.com/bepass-org/ipscanner v0.0.0-20240205155121-8927b7437d16 h1:Fa/PA9jH+S3yzGZhPHhJl+DvmReOq3BIB+5OzhJSLTQ= -github.com/bepass-org/ipscanner v0.0.0-20240205155121-8927b7437d16/go.mod h1:ZDON74kRVPv/FSJPzoqYAyffLdZf+pjZqYGXrebHrxI= github.com/bepass-org/proxy v0.0.0-20240201095508-c86216dd0aea h1:6GKkjxDUxqq7uwA8U15N4PFURhdNN0OrxFuXc58MGUU= github.com/bepass-org/proxy v0.0.0-20240201095508-c86216dd0aea/go.mod h1:RlF0oO3D6Ju6VYjtL1I6lVLdc3l8jA4ggleJc8S+P0Y= -github.com/bepass-org/psiphon-tunnel-core v0.0.0-20240223214330-9783d71283bc h1:KIQYHx0JeiV8U/e0aR8ss3MZP2jmVr3/xIisjmDQrXY= -github.com/bepass-org/psiphon-tunnel-core v0.0.0-20240223214330-9783d71283bc/go.mod h1:PfrO3bCrr/K6NLUZWIdqVBV66SWLI7bfdGaJe2BJ2I8= +github.com/bepass-org/psiphon-tunnel-core v0.0.0-20240311155012-9c2e10df08e5 h1:UVdsUQXhviRMzVA02BGzEHUYUBAAeSJYijqKWJvMCxs= +github.com/bepass-org/psiphon-tunnel-core v0.0.0-20240311155012-9c2e10df08e5/go.mod h1:vA5iCui7nfavWyBN8MsLYZ5xpKItjrTvPC0SuMWz48Q= github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61 h1:BU+NxuoaYPIvvp8NNkNlLr8aA0utGyuunf4Q3LJ0bh0= github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk= github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -40,41 +35,57 @@ 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= github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed h1:njG8LmGD6JCWJu4bwIKmkOHvch70UOEIqczl5vp7Gok= +github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273 h1:45qZ7jowabqhyi3l9Ervox4dhQvLGB5BJPdC8w0a77k= github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 h1:afESQBXJEnj3fu+34X//E8Wg3nEbMJxJkwSc0tPePK0= github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d h1:rtM8HsT3NG37YPjz8sYSbUSdElP9lUsQENYzJDZDUBE= +github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d h1:st1tmvy+4duoRj+RaeeJoECWCWM015fBtf/4aR+hhqk= +github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0 h1:7ZJyJV4KiWBijCCzUPvVaqxsDxO36+KD0XKBdEN3I+8= +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= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439 h1:T6zlOdzrYuHf6HUKujm9bzkzbZ5Iv/xf6rs8BHZDpoI= +github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +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= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 h1:xh96CCAZTX8LJPFoOVRgTwZbn2DvJl8fyCyivohhSIg= @@ -83,21 +94,34 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= +github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= 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= +github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a/go.mod h1:TmeOqAKoDinfPfSohs14CO3VcEf7o+Bem6JiNe05yrQ= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mdlayher/netlink v1.4.2-0.20210930205308-a81a8c23d40a h1:yk5OmRew64lWdeNanQ3l0hDgUt1E8MfipPhh/GO9Tuw= +github.com/mdlayher/netlink v1.4.2-0.20210930205308-a81a8c23d40a/go.mod h1:qw8F9IVzxa0GpqhVAfOw8DNyo7ec/jxI6bPWPEg1MV4= github.com/mdlayher/socket v0.0.0-20210624160740-9dbe287ded84 h1:L1jnQ6o+K3M574eez7eTxbsia6H1SfJaVpaXY33L37Q= +github.com/mdlayher/socket v0.0.0-20210624160740-9dbe287ded84/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc= github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300 h1:cpzamikkKRyu3TZF14CsVFf/CmhlrqZ+7P9aVZYtXz8= github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E= @@ -105,13 +129,19 @@ github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJ github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= +github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pebbe/zmq4 v1.2.10 h1:wQkqRZ3CZeABIeidr3e8uQZMMH5YAykA/WN0L5zkd1c= +github.com/pebbe/zmq4 v1.2.10/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 h1:aiqS8aBlF9PsAKeMddMSfbwp3smONCn3UO8QfUg0Z7Y= github.com/peterbourgon/ff/v4 v4.0.0-alpha.4/go.mod h1:H/13DK46DKXy7EaIxPhk2Y0EC8aubKm35nBjBe8AAGc= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= @@ -127,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= @@ -147,7 +178,14 @@ github.com/refraction-networking/obfs4 v0.1.2 h1:J842O4fGSkd2W8ogYj0KN6gqVVY+Cpq github.com/refraction-networking/obfs4 v0.1.2/go.mod h1:wAl/+gWiLsrcykJA3nKJHx89f5/gXGM8UKvty7+mvbM= github.com/refraction-networking/utls v1.3.3 h1:f/TBLX7KBciRyFH3bwupp+CE4fzoYKCirhdRcC490sw= github.com/refraction-networking/utls v1.3.3/go.mod h1:DlecWW1LMlMJu+9qpzzQqdHDT/C2LAe03EdpLUz/RL8= +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= github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507/go.mod h1:DbI1gxrXI2jRGw7XGEUZQOOMd6PsnKzRrCKabvvMrwM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -176,8 +214,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM= @@ -193,13 +231,14 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -209,19 +248,22 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -243,6 +285,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= @@ -252,9 +295,11 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= honnef.co/go/tools v0.4.2 h1:6qXr+R5w+ktL5UkwEbPp+fEvfyoMPche6GkOpGHZcLc= +honnef.co/go/tools v0.4.2/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= diff --git a/ipscanner/LICENSE b/ipscanner/LICENSE new file mode 100644 index 000000000..8dea11b54 --- /dev/null +++ b/ipscanner/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Bepass + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ipscanner/README.md b/ipscanner/README.md new file mode 100644 index 000000000..a39446f28 --- /dev/null +++ b/ipscanner/README.md @@ -0,0 +1,38 @@ +# IPScanner + +IPScanner is a Go package designed for scanning and analyzing IP addresses. It utilizes various dialers and an internal engine to perform scans efficiently. + +## Features +- IPv4 and IPv6 support. +- Customizable timeout and dialer options. +- Extendable with various ping methods (HTTP, QUIC, TCP, TLS). +- Adjustable IP Queue size for scan optimization. + +## Getting Started +To use IPScanner, simply import the package and initialize a new scanner with your desired options. + +```go +import "github.com/bepass-org/wireguard-go/ipscanner" + +func main() { + scanner := ipscanner.NewScanner( + // Configure your options here + ) + scanner.Run() +} +``` + +## Options +You can customize your scanner with several options: +- `WithUseIPv4` and `WithUseIPv6` to specify IP versions. +- `WithDialer` and `WithTLSDialer` to define custom dialing functions. +- `WithTimeout` to set the scan timeout. +- `WithIPQueueSize` to set the IP Queue size. +- `WithPingMethod` to set the ping method, it can be HTTP, QUIC, TCP, TLS at the same time. +- Various other options for detailed scan control. + +## Contributing +Contributions to IPScanner are welcome. Please ensure to follow the project's coding standards and submit detailed pull requests. + +## License +IPScanner is licensed under the MIT license. See [LICENSE](LICENSE) for more information. diff --git a/ipscanner/example/cfscanner/main.go b/ipscanner/example/cfscanner/main.go new file mode 100644 index 000000000..a87796135 --- /dev/null +++ b/ipscanner/example/cfscanner/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "context" + + "github.com/bepass-org/wireguard-go/ipscanner" +) + +func main() { + // new scanner + scanner := ipscanner.NewScanner( + ipscanner.WithHTTPPing(), + ipscanner.WithUseIPv6(true), + ) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go scanner.Run(ctx) + + <-ctx.Done() +} diff --git a/ipscanner/example/warpscanner/main.go b/ipscanner/example/warpscanner/main.go new file mode 100644 index 000000000..cb3d266f5 --- /dev/null +++ b/ipscanner/example/warpscanner/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "context" + "log/slog" + "net" + "net/netip" + "os" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner" + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" + "github.com/bepass-org/wireguard-go/warp" + "github.com/fatih/color" + "github.com/rodaine/table" +) + +var ( + privKey = "yGXeX7gMyUIZmK5QIgC7+XX5USUSskQvBYiQ6LdkiXI=" + pubKey = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=" + googlev6DNSAddr80 = netip.MustParseAddrPort("[2001:4860:4860::8888]:80") +) + +func canConnectIPv6(remoteAddr netip.AddrPort) bool { + dialer := net.Dialer{ + Timeout: 5 * time.Second, + } + + conn, err := dialer.Dial("tcp6", remoteAddr.String()) + if err != nil { + return false + } + defer conn.Close() + + return true +} + +func RunScan(privKey, pubKey string) (result []statute.IPInfo) { + // new scanner + scanner := ipscanner.NewScanner( + ipscanner.WithLogger(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))), + ipscanner.WithWarpPing(), + ipscanner.WithWarpPrivateKey(privKey), + ipscanner.WithWarpPeerPublicKey(pubKey), + ipscanner.WithUseIPv6(canConnectIPv6(googlev6DNSAddr80)), + ipscanner.WithUseIPv4(true), + ipscanner.WithMaxDesirableRTT(500*time.Millisecond), + ipscanner.WithCidrList(warp.WarpPrefixes()), + ) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + scanner.Run(ctx) + + t := time.NewTicker(1 * time.Second) + defer t.Stop() + + for { + ipList := scanner.GetAvailableIPs() + if len(ipList) > 1 { + for i := 0; i < 2; i++ { + result = append(result, ipList[i]) + } + return + } + + select { + case <-ctx.Done(): + // Context is done + return + case <-t.C: + // Prevent the loop from spinning too fast + continue + } + } +} + +func main() { + result := RunScan(privKey, pubKey) + + headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() + columnFmt := color.New(color.FgYellow).SprintfFunc() + + tbl := table.New("Address", "RTT (ping)", "Time") + tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) + + for _, info := range result { + tbl.AddRow(info.AddrPort, info.RTT, info.CreatedAt) + } + + tbl.Print() +} diff --git a/ipscanner/internal/cache/cache.go b/ipscanner/internal/cache/cache.go new file mode 100644 index 000000000..ec38ea2a0 --- /dev/null +++ b/ipscanner/internal/cache/cache.go @@ -0,0 +1,60 @@ +package cache + +import ( + "math/big" + "net/netip" + "sync" + + "github.com/hashicorp/golang-lru" +) + +type BiDirectionalCache struct { + ipToBigIntCache *lru.Cache + bigIntToIPCache *lru.Cache + lock sync.RWMutex +} + +func NewBiDirectionalCache(size int) (*BiDirectionalCache, error) { + ipToBigInt, err := lru.New(size) + if err != nil { + return nil, err + } + + bigIntToIP, err := lru.New(size) + if err != nil { + return nil, err + } + + return &BiDirectionalCache{ + ipToBigIntCache: ipToBigInt, + bigIntToIPCache: bigIntToIP, + }, nil +} + +func (c *BiDirectionalCache) GetIPFromBigInt(bigIntKey string) (netip.Addr, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + + if ip, found := c.bigIntToIPCache.Get(bigIntKey); found { + return ip.(netip.Addr), true + } + return netip.Addr{}, false +} + +func (c *BiDirectionalCache) GetBigIntFromIP(ip netip.Addr) (*big.Int, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + + if bigInt, found := c.ipToBigIntCache.Get(ip.String()); found { + return bigInt.(*big.Int), true + } + return nil, false +} + +func (c *BiDirectionalCache) PutIPAndBigInt(ip netip.Addr, bigInt *big.Int) { + c.lock.Lock() + defer c.lock.Unlock() + + c.ipToBigIntCache.Add(ip.String(), bigInt) + c.bigIntToIPCache.Add(bigInt.String(), ip) +} diff --git a/ipscanner/internal/engine/engine.go b/ipscanner/internal/engine/engine.go new file mode 100644 index 000000000..e6f9aea66 --- /dev/null +++ b/ipscanner/internal/engine/engine.go @@ -0,0 +1,76 @@ +package engine + +import ( + "context" + "log/slog" + "net/netip" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/iterator" + "github.com/bepass-org/wireguard-go/ipscanner/internal/ping" + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type Engine struct { + generator *iterator.IpGenerator + ipQueue *IPQueue + ping func(netip.Addr) (statute.IPInfo, error) + log *slog.Logger +} + +func NewScannerEngine(opts *statute.ScannerOptions) *Engine { + queue := NewIPQueue(opts) + + p := ping.Ping{ + Options: opts, + } + return &Engine{ + ipQueue: queue, + ping: p.DoPing, + generator: iterator.NewIterator(opts), + log: opts.Logger.With(slog.String("subsystem", "engine")), + } +} + +func (e *Engine) GetAvailableIPs(desc bool) []statute.IPInfo { + if e.ipQueue != nil { + return e.ipQueue.AvailableIPs(desc) + } + return nil +} + +func (e *Engine) Run(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case <-e.ipQueue.available: + e.log.Debug("Started new scanning round") + batch, err := e.generator.NextBatch() + if err != nil { + e.log.Error("Error while generating IP: %v", err) + // in case of disastrous error, to prevent resource draining wait for 2 seconds and try again + time.Sleep(2 * time.Second) + continue + } + for _, ip := range batch { + select { + case <-ctx.Done(): + return + default: + e.log.Debug("pinging IP", "addr", ip) + if ipInfo, err := e.ping(ip); err == nil { + e.log.Debug("ping success", "addr", ipInfo.AddrPort, "rtt", ipInfo.RTT) + e.ipQueue.Enqueue(ipInfo) + } else { + e.log.Error("ping error", "addr", ip, "error", err) + } + } + } + default: + e.log.Debug("calling expire") + e.ipQueue.Expire() + time.Sleep(200 * time.Millisecond) + } + } +} diff --git a/ipscanner/internal/engine/queue.go b/ipscanner/internal/engine/queue.go new file mode 100644 index 000000000..6436fec91 --- /dev/null +++ b/ipscanner/internal/engine/queue.go @@ -0,0 +1,185 @@ +package engine + +import ( + "log/slog" + "sort" + "sync" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type IPQueue struct { + queue []statute.IPInfo + maxQueueSize int + mu sync.Mutex + available chan struct{} + maxTTL time.Duration + rttThreshold time.Duration + inIdealMode bool + log *slog.Logger + reserved statute.IPInfQueue +} + +func NewIPQueue(opts *statute.ScannerOptions) *IPQueue { + var reserved statute.IPInfQueue + return &IPQueue{ + queue: make([]statute.IPInfo, 0), + maxQueueSize: opts.IPQueueSize, + maxTTL: opts.IPQueueTTL, + rttThreshold: opts.MaxDesirableRTT, + available: make(chan struct{}, opts.IPQueueSize), + log: opts.Logger.With(slog.String("subsystem", "engine/queue")), + reserved: reserved, + } +} + +func (q *IPQueue) Enqueue(info statute.IPInfo) bool { + q.mu.Lock() + defer q.mu.Unlock() + + defer func() { + q.log.Debug("queue change", "len", len(q.queue)) + for _, ipInfo := range q.queue { + q.log.Debug( + "queue change", + "created", ipInfo.CreatedAt, + "addr", ipInfo.AddrPort, + "rtt", ipInfo.RTT, + ) + } + }() + + q.log.Debug("Enqueue: Sorting queue by RTT") + sort.Slice(q.queue, func(i, j int) bool { + return q.queue[i].RTT < q.queue[j].RTT + }) + + if len(q.queue) == 0 { + q.log.Debug("Enqueue: empty queue adding first available item") + q.queue = append(q.queue, info) + return false + } + + if info.RTT <= q.rttThreshold { + q.log.Debug("Enqueue: the new item's RTT is less than at least one of the members.") + if len(q.queue) >= q.maxQueueSize && info.RTT < q.queue[len(q.queue)-1].RTT { + q.log.Debug("Enqueue: the queue is full, remove the item with the highest RTT.") + q.queue = q.queue[:len(q.queue)-1] + } else if len(q.queue) < q.maxQueueSize { + q.log.Debug("Enqueue: Insert the new item in a sorted position.") + index := sort.Search(len(q.queue), func(i int) bool { return q.queue[i].RTT > info.RTT }) + q.queue = append(q.queue[:index], append([]statute.IPInfo{info}, q.queue[index:]...)...) + } else { + q.log.Debug("Enqueue: The Queue is full but we keep the new item in the reserved queue.") + q.reserved.Enqueue(info) + } + } + + q.log.Debug("Enqueue: Checking if any member has a higher RTT than the threshold.") + for _, member := range q.queue { + if member.RTT > q.rttThreshold { + return false // If any member has a higher RTT than the threshold, return false. + } + } + + q.log.Debug("Enqueue: All members have an RTT lower than the threshold.") + if len(q.queue) < q.maxQueueSize { + // the queue isn't full dont wait + return false + } + + q.inIdealMode = true + // ok wait for expiration signal + q.log.Debug("Enqueue: All members have an RTT lower than the threshold. Waiting for expiration signal.") + return true +} + +func (q *IPQueue) Dequeue() (statute.IPInfo, bool) { + defer func() { + q.log.Debug("queue change", "len", len(q.queue)) + for _, ipInfo := range q.queue { + q.log.Debug( + "queue change", + "created", ipInfo.CreatedAt, + "addr", ipInfo.AddrPort, + "rtt", ipInfo.RTT, + ) + } + }() + q.mu.Lock() + defer q.mu.Unlock() + + if len(q.queue) == 0 { + return statute.IPInfo{}, false + } + + info := q.queue[len(q.queue)-1] + q.queue = q.queue[0 : len(q.queue)-1] + + q.available <- struct{}{} + + return info, true +} + +func (q *IPQueue) Expire() { + q.mu.Lock() + defer q.mu.Unlock() + + if !q.inIdealMode { + q.log.Debug("Expire: Not in ideal mode") + q.available <- struct{}{} + return + } + + q.log.Debug("Expire: In ideal mode") + defer func() { + q.log.Debug("queue change", "len", len(q.queue)) + for _, ipInfo := range q.queue { + q.log.Debug( + "queue change", + "created", ipInfo.CreatedAt, + "addr", ipInfo.AddrPort, + "rtt", ipInfo.RTT, + ) + } + }() + + shouldStartNewScan := false + resQ := make([]statute.IPInfo, 0) + for i := 0; i < len(q.queue); i++ { + if time.Since(q.queue[i].CreatedAt) > q.maxTTL { + q.log.Debug("Expire: Removing expired item from queue") + shouldStartNewScan = true + } else { + resQ = append(resQ, q.queue[i]) + } + } + q.queue = resQ + q.log.Debug("Expire: Adding reserved items to queue") + for i := 0; i < q.maxQueueSize && i < q.reserved.Size(); i++ { + q.queue = append(q.queue, q.reserved.Dequeue()) + } + if shouldStartNewScan { + q.available <- struct{}{} + } +} + +func (q *IPQueue) AvailableIPs(desc bool) []statute.IPInfo { + q.mu.Lock() + defer q.mu.Unlock() + + // Create a separate slice for sorting + sortedQueue := make([]statute.IPInfo, len(q.queue)) + copy(sortedQueue, q.queue) + + // Sort by RTT ascending/descending + sort.Slice(sortedQueue, func(i, j int) bool { + if desc { + return sortedQueue[i].RTT > sortedQueue[j].RTT + } + return sortedQueue[i].RTT < sortedQueue[j].RTT + }) + + return sortedQueue +} diff --git a/ipscanner/internal/iterator/iterator.go b/ipscanner/internal/iterator/iterator.go new file mode 100644 index 000000000..0ec185d62 --- /dev/null +++ b/ipscanner/internal/iterator/iterator.go @@ -0,0 +1,281 @@ +package iterator + +import ( + "crypto/rand" + "errors" + "fmt" + "log" + "math/big" + "net" + "net/netip" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/cache" + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +// LCG represents a linear congruential generator with full period. +type LCG struct { + modulus *big.Int + multiplier *big.Int + increment *big.Int + current *big.Int +} + +// NewLCG creates a new LCG instance with a given size. +func NewLCG(size *big.Int) *LCG { + modulus := new(big.Int).Set(size) + + // Generate random multiplier (a) and increment (c) that satisfy Hull-Dobell Theorem + var multiplier, increment *big.Int + for { + var err error + multiplier, err = rand.Int(rand.Reader, modulus) + if err != nil { + continue + } + increment, err = rand.Int(rand.Reader, modulus) + if err != nil { + continue + } + + // Check Hull-Dobell Theorem conditions + if checkHullDobell(modulus, multiplier, increment) { + break + } + } + + return &LCG{ + modulus: modulus, + multiplier: multiplier, + increment: increment, + current: big.NewInt(0), + } +} + +// checkHullDobell checks if the given parameters satisfy the Hull-Dobell Theorem. +func checkHullDobell(modulus, multiplier, increment *big.Int) bool { + // c and m are relatively prime + gcd := new(big.Int).GCD(nil, nil, increment, modulus) + if gcd.Cmp(big.NewInt(1)) != 0 { + return false + } + + // a - 1 is divisible by all prime factors of m + aMinusOne := new(big.Int).Sub(multiplier, big.NewInt(1)) + + // a - 1 is divisible by 4 if m is divisible by 4 + if new(big.Int).And(modulus, big.NewInt(3)).Cmp(big.NewInt(0)) == 0 { + if new(big.Int).And(aMinusOne, big.NewInt(3)).Cmp(big.NewInt(0)) != 0 { + return false + } + } + + return true +} + +// Next generates the next number in the sequence. +func (lcg *LCG) Next() *big.Int { + if lcg.current.Cmp(lcg.modulus) == 0 { + return nil // Sequence complete + } + + next := new(big.Int) + next.Mul(lcg.multiplier, lcg.current) + next.Add(next, lcg.increment) + next.Mod(next, lcg.modulus) + + lcg.current.Set(next) + return next +} + +type ipRange struct { + lcg *LCG + start netip.Addr + stop netip.Addr + size *big.Int + index *big.Int +} + +func newIPRange(cidr netip.Prefix) (ipRange, error) { + startIP := cidr.Addr() + stopIP := lastIP(cidr) + size := ipRangeSize(cidr) + return ipRange{ + start: startIP, + stop: stopIP, + size: size, + index: big.NewInt(0), + lcg: NewLCG(size), + }, nil +} + +func lastIP(prefix netip.Prefix) netip.Addr { + // Calculate the number of bits to fill for the last address based on the address family + fillBits := 128 - prefix.Bits() + if prefix.Addr().Is4() { + fillBits = 32 - prefix.Bits() + } + + // Calculate the numerical representation of the last address by setting the remaining bits to 1 + var lastAddrInt big.Int + lastAddrInt.SetBytes(prefix.Addr().AsSlice()) + for i := 0; i < fillBits; i++ { + lastAddrInt.SetBit(&lastAddrInt, i, 1) + } + + // Convert the big.Int back to netip.Addr + lastAddrBytes := lastAddrInt.Bytes() + var lastAddr netip.Addr + if prefix.Addr().Is4() { + // Ensure the slice is the right length for IPv4 + if len(lastAddrBytes) < net.IPv4len { + leadingZeros := make([]byte, net.IPv4len-len(lastAddrBytes)) + lastAddrBytes = append(leadingZeros, lastAddrBytes...) + } + lastAddr, _ = netip.AddrFromSlice(lastAddrBytes[len(lastAddrBytes)-net.IPv4len:]) + } else { + // Ensure the slice is the right length for IPv6 + if len(lastAddrBytes) < net.IPv6len { + leadingZeros := make([]byte, net.IPv6len-len(lastAddrBytes)) + lastAddrBytes = append(leadingZeros, lastAddrBytes...) + } + lastAddr, _ = netip.AddrFromSlice(lastAddrBytes) + } + + return lastAddr +} + +var biDirectionalCache, _ = cache.NewBiDirectionalCache(big.MaxBase) // initial cache size + +func ipToBigInt(ip netip.Addr) *big.Int { + if bigInt, found := biDirectionalCache.GetBigIntFromIP(ip); found { + return bigInt + } + result := new(big.Int).SetBytes(ip.AsSlice()) + biDirectionalCache.PutIPAndBigInt(ip, result) + return result +} + +func bigIntToIP(n *big.Int) netip.Addr { + if ip, found := biDirectionalCache.GetIPFromBigInt(n.String()); found { + return ip + } + ipBytes := n.Bytes() + var lastAddr netip.Addr + if len(ipBytes) <= net.IPv4len { // Adjust for IPv4 + if len(ipBytes) < net.IPv4len { + ipBytes = append(make([]byte, net.IPv4len-len(ipBytes)), ipBytes...) + } + lastAddr, _ = netip.AddrFromSlice(ipBytes) + } else { // Adjust for IPv6 + if len(ipBytes) < net.IPv6len { + ipBytes = append(make([]byte, net.IPv6len-len(ipBytes)), ipBytes...) + } + lastAddr, _ = netip.AddrFromSlice(ipBytes) + } + biDirectionalCache.PutIPAndBigInt(lastAddr, n) + return lastAddr +} + +func addIP(ip netip.Addr, num *big.Int) netip.Addr { + ipInt := ipToBigInt(ip) + ipInt.Add(ipInt, num) + return bigIntToIP(ipInt) +} + +func ipRangeSize(prefix netip.Prefix) *big.Int { + // The number of bits in the address depends on whether it's IPv4 or IPv6. + totalBits := 128 // Assume IPv6 by default + if prefix.Addr().Is4() { + totalBits = 32 // Adjust for IPv4 + } + + // Calculate the size of the range + bits := prefix.Bits() // This is the prefix length + size := big.NewInt(1) + size.Lsh(size, uint(totalBits-bits)) // Left shift to calculate the range size + + return size +} + +type IpGenerator struct { + ipRanges []ipRange +} + +func (g *IpGenerator) NextBatch() ([]netip.Addr, error) { + var results []netip.Addr + for i, r := range g.ipRanges { + if r.index.Cmp(r.size) >= 0 { + continue + } + shuffleIndex := r.lcg.Next() + if shuffleIndex == nil { + continue + } + results = append(results, addIP(r.start, shuffleIndex)) + g.ipRanges[i].index.Add(g.ipRanges[i].index, big.NewInt(1)) + } + if len(results) == 0 { + okFlag := false + for i := range g.ipRanges { + if g.ipRanges[i].index.Cmp(big.NewInt(0)) > 0 { + okFlag = true + } + g.ipRanges[i].index.SetInt64(0) + } + if okFlag { + // Reshuffle and start over + for i := range g.ipRanges { + g.ipRanges[i].lcg = NewLCG(g.ipRanges[i].size) + } + return g.NextBatch() + } else { + return nil, errors.New("no more IP addresses") + } + } + return results, nil +} + +// shuffleSubnetsIpRange shuffles a slice of ipRange using crypto/rand +func shuffleSubnetsIpRange(subnets []ipRange) error { + for i := range subnets { + jBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(subnets)))) + if err != nil { + return err + } + j := jBig.Int64() + + subnets[i], subnets[j] = subnets[j], subnets[i] + } + return nil +} + +func NewIterator(opts *statute.ScannerOptions) *IpGenerator { + var ranges []ipRange + for _, cidr := range opts.CidrList { + if !opts.UseIPv6 && cidr.Addr().Is6() { + continue + } + if !opts.UseIPv4 && cidr.Addr().Is4() { + continue + } + + ipRange, err := newIPRange(cidr) + if err != nil { + fmt.Printf("Error parsing CIDR %s: %v\n", cidr, err) + continue + } + ranges = append(ranges, ipRange) + } + if len(ranges) == 0 { + log.Fatal("No valid CIDR ranges found") + } + err := shuffleSubnetsIpRange(ranges) + if err != nil { + fmt.Println(err) + return nil + } + return &IpGenerator{ + ipRanges: ranges, + } +} diff --git a/ipscanner/internal/ping/http.go b/ipscanner/internal/ping/http.go new file mode 100644 index 000000000..11a4f5201 --- /dev/null +++ b/ipscanner/internal/ping/http.go @@ -0,0 +1,128 @@ +package ping + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/netip" + "net/url" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type HttpPingResult struct { + AddrPort netip.AddrPort + Proto string + Status int + Length int + RTT time.Duration + Err error +} + +func (h *HttpPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: h.AddrPort, RTT: h.RTT, CreatedAt: time.Now()} +} + +func (h *HttpPingResult) Error() error { + return h.Err +} + +func (h *HttpPingResult) String() string { + if h.Err != nil { + return fmt.Sprintf("%s", h.Err) + } + + return fmt.Sprintf("%s: protocol=%s, status=%d, length=%d, time=%d ms", h.AddrPort, h.Proto, h.Status, h.Length, h.RTT) +} + +type HttpPing struct { + Method string + URL string + IP netip.Addr + + opts statute.ScannerOptions +} + +func (h *HttpPing) Ping() statute.IPingResult { + return h.PingContext(context.Background()) +} + +func (h *HttpPing) PingContext(ctx context.Context) statute.IPingResult { + u, err := url.Parse(h.URL) + if err != nil { + return h.errorResult(err) + } + orighost := u.Host + + if !h.IP.IsValid() { + return h.errorResult(errors.New("no IP specified")) + } + + req, err := http.NewRequestWithContext(ctx, h.Method, h.URL, nil) + if err != nil { + return h.errorResult(err) + } + ua := "httping" + if h.opts.UserAgent != "" { + ua = h.opts.UserAgent + } + req.Header.Set("User-Agent", ua) + if h.opts.Referrer != "" { + req.Header.Set("Referer", h.opts.Referrer) + } + req.Host = orighost + + addr := netip.AddrPortFrom(h.IP, h.opts.Port) + client := h.opts.HttpClientFunc(h.opts.RawDialerFunc, h.opts.TLSDialerFunc, h.opts.QuicDialerFunc, addr.String()) + + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + + t0 := time.Now() + resp, err := client.Do(req) + if err != nil { + return h.errorResult(err) + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return h.errorResult(err) + } + + res := HttpPingResult{ + AddrPort: addr, + Proto: resp.Proto, + Status: resp.StatusCode, + Length: len(body), + RTT: time.Since(t0), + Err: nil, + } + + return &res +} + +func (h *HttpPing) errorResult(err error) *HttpPingResult { + r := &HttpPingResult{} + r.Err = err + return r +} + +func NewHttpPing(ip netip.Addr, method, url string, opts *statute.ScannerOptions) *HttpPing { + return &HttpPing{ + IP: ip, + Method: method, + URL: url, + + opts: *opts, + } +} + +var ( + _ statute.IPing = (*HttpPing)(nil) + _ statute.IPingResult = (*HttpPingResult)(nil) +) diff --git a/ipscanner/internal/ping/ping.go b/ipscanner/internal/ping/ping.go new file mode 100644 index 000000000..794c2e732 --- /dev/null +++ b/ipscanner/internal/ping/ping.go @@ -0,0 +1,106 @@ +package ping + +import ( + "errors" + "fmt" + "net/netip" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type Ping struct { + Options *statute.ScannerOptions +} + +// DoPing performs a ping on the given IP address. +func (p *Ping) DoPing(ip netip.Addr) (statute.IPInfo, error) { + if p.Options.SelectedOps&statute.HTTPPing > 0 { + res, err := p.httpPing(ip) + if err != nil { + return statute.IPInfo{}, err + } + + return res, nil + } + if p.Options.SelectedOps&statute.TLSPing > 0 { + res, err := p.tlsPing(ip) + if err != nil { + return statute.IPInfo{}, err + } + + return res, nil + } + if p.Options.SelectedOps&statute.TCPPing > 0 { + res, err := p.tcpPing(ip) + if err != nil { + return statute.IPInfo{}, err + } + + return res, nil + } + if p.Options.SelectedOps&statute.QUICPing > 0 { + res, err := p.quicPing(ip) + if err != nil { + return statute.IPInfo{}, err + } + + return res, nil + } + if p.Options.SelectedOps&statute.WARPPing > 0 { + res, err := p.warpPing(ip) + if err != nil { + return statute.IPInfo{}, err + } + + return res, nil + } + + return statute.IPInfo{}, errors.New("no ping operation selected") +} + +func (p *Ping) httpPing(ip netip.Addr) (statute.IPInfo, error) { + return p.calc( + NewHttpPing( + ip, + "GET", + fmt.Sprintf( + "https://%s:%d%s", + p.Options.Hostname, + p.Options.Port, + p.Options.HTTPPath, + ), + p.Options, + ), + ) +} + +func (p *Ping) warpPing(ip netip.Addr) (statute.IPInfo, error) { + return p.calc(NewWarpPing(ip, p.Options)) +} + +func (p *Ping) tlsPing(ip netip.Addr) (statute.IPInfo, error) { + return p.calc( + NewTlsPing(ip, p.Options.Hostname, p.Options.Port, p.Options), + ) +} + +func (p *Ping) tcpPing(ip netip.Addr) (statute.IPInfo, error) { + return p.calc( + NewTcpPing(ip, p.Options.Hostname, p.Options.Port, p.Options), + ) +} + +func (p *Ping) quicPing(ip netip.Addr) (statute.IPInfo, error) { + return p.calc( + NewQuicPing(ip, p.Options.Hostname, p.Options.Port, p.Options), + ) +} + +func (p *Ping) calc(tp statute.IPing) (statute.IPInfo, error) { + pr := tp.Ping() + err := pr.Error() + if err != nil { + return statute.IPInfo{}, err + } + return pr.Result(), nil +} diff --git a/ipscanner/internal/ping/quic.go b/ipscanner/internal/ping/quic.go new file mode 100644 index 000000000..92ca9e5e6 --- /dev/null +++ b/ipscanner/internal/ping/quic.go @@ -0,0 +1,95 @@ +package ping + +import ( + "context" + "errors" + "fmt" + "net/netip" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" + + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" +) + +type QuicPingResult struct { + AddrPort netip.AddrPort + QUICVersion quic.VersionNumber + TLSVersion uint16 + RTT time.Duration + Err error +} + +func (h *QuicPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: h.AddrPort, RTT: h.RTT, CreatedAt: time.Now()} +} + +func (h *QuicPingResult) Error() error { + return h.Err +} + +func (h *QuicPingResult) String() string { + if h.Err != nil { + return fmt.Sprintf("%s", h.Err) + } + + return fmt.Sprintf("%s: quic=%s, tls=%s, time=%d ms", h.AddrPort, quic.VersionNumber(h.QUICVersion), statute.TlsVersionToString(h.TLSVersion), h.RTT) +} + +type QuicPing struct { + Host string + Port uint16 + IP netip.Addr + + opts statute.ScannerOptions +} + +func (h *QuicPing) Ping() statute.IPingResult { + return h.PingContext(context.Background()) +} + +func (h *QuicPing) PingContext(ctx context.Context) statute.IPingResult { + if !h.IP.IsValid() { + return h.errorResult(errors.New("no IP specified")) + } + + addr := netip.AddrPortFrom(h.IP, h.Port) + + t0 := time.Now() + conn, err := h.opts.QuicDialerFunc(ctx, addr.String(), nil, nil) + if err != nil { + return h.errorResult(err) + } + + res := QuicPingResult{ + AddrPort: addr, + RTT: time.Since(t0), + QUICVersion: conn.ConnectionState().Version, + TLSVersion: conn.ConnectionState().TLS.Version, + Err: nil, + } + + defer conn.CloseWithError(quic.ApplicationErrorCode(uint64(http3.ErrCodeNoError)), "") + return &res +} + +func NewQuicPing(ip netip.Addr, host string, port uint16, opts *statute.ScannerOptions) *QuicPing { + return &QuicPing{ + IP: ip, + Host: host, + Port: port, + opts: *opts, + } +} + +func (h *QuicPing) errorResult(err error) *QuicPingResult { + r := &QuicPingResult{} + r.Err = err + return r +} + +var ( + _ statute.IPing = (*QuicPing)(nil) + _ statute.IPingResult = (*QuicPingResult)(nil) +) diff --git a/ipscanner/internal/ping/tcp.go b/ipscanner/internal/ping/tcp.go new file mode 100644 index 000000000..2ef29150d --- /dev/null +++ b/ipscanner/internal/ping/tcp.go @@ -0,0 +1,84 @@ +package ping + +import ( + "context" + "errors" + "fmt" + "net/netip" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type TcpPingResult struct { + AddrPort netip.AddrPort + RTT time.Duration + Err error +} + +func (tp *TcpPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: tp.AddrPort, RTT: tp.RTT, CreatedAt: time.Now()} +} + +func (tp *TcpPingResult) Error() error { + return tp.Err +} + +func (tp *TcpPingResult) String() string { + if tp.Err != nil { + return fmt.Sprintf("%s", tp.Err) + } else { + return fmt.Sprintf("%s: time=%d ms", tp.AddrPort, tp.RTT) + } +} + +type TcpPing struct { + host string + port uint16 + ip netip.Addr + + opts statute.ScannerOptions +} + +func (tp *TcpPing) SetHost(host string) { + tp.host = host + tp.ip, _ = netip.ParseAddr(host) +} + +func (tp *TcpPing) Host() string { + return tp.host +} + +func (tp *TcpPing) Ping() statute.IPingResult { + return tp.PingContext(context.Background()) +} + +func (tp *TcpPing) PingContext(ctx context.Context) statute.IPingResult { + if !tp.ip.IsValid() { + return &TcpPingResult{AddrPort: netip.AddrPort{}, RTT: 0, Err: errors.New("no IP specified")} + } + + addr := netip.AddrPortFrom(tp.ip, tp.port) + t0 := time.Now() + conn, err := tp.opts.RawDialerFunc(ctx, "tcp", addr.String()) + if err != nil { + return &TcpPingResult{AddrPort: addr, RTT: 0, Err: err} + } + defer conn.Close() + + return &TcpPingResult{AddrPort: addr, RTT: time.Since(t0), Err: nil} +} + +func NewTcpPing(ip netip.Addr, host string, port uint16, opts *statute.ScannerOptions) *TcpPing { + return &TcpPing{ + host: host, + port: port, + ip: ip, + opts: *opts, + } +} + +var ( + _ statute.IPing = (*TcpPing)(nil) + _ statute.IPingResult = (*TcpPingResult)(nil) +) diff --git a/ipscanner/internal/ping/tls.go b/ipscanner/internal/ping/tls.go new file mode 100644 index 000000000..f2438a9a7 --- /dev/null +++ b/ipscanner/internal/ping/tls.go @@ -0,0 +1,80 @@ +package ping + +import ( + "context" + "errors" + "fmt" + "net/netip" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type TlsPingResult struct { + AddrPort netip.AddrPort + TLSVersion uint16 + RTT time.Duration + Err error +} + +func (t *TlsPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: t.AddrPort, RTT: t.RTT, CreatedAt: time.Now()} +} + +func (t *TlsPingResult) Error() error { + return t.Err +} + +func (t *TlsPingResult) String() string { + if t.Err != nil { + return fmt.Sprintf("%s", t.Err) + } + + return fmt.Sprintf("%s: protocol=%s, time=%d ms", t.AddrPort, statute.TlsVersionToString(t.TLSVersion), t.RTT) +} + +type TlsPing struct { + Host string + Port uint16 + IP netip.Addr + + opts *statute.ScannerOptions +} + +func (t *TlsPing) Ping() statute.IPingResult { + return t.PingContext(context.Background()) +} + +func (t *TlsPing) PingContext(ctx context.Context) statute.IPingResult { + if !t.IP.IsValid() { + return t.errorResult(errors.New("no IP specified")) + } + addr := netip.AddrPortFrom(t.IP, t.Port) + t0 := time.Now() + client, err := t.opts.TLSDialerFunc(ctx, "tcp", addr.String()) + if err != nil { + return t.errorResult(err) + } + defer client.Close() + return &TlsPingResult{AddrPort: addr, TLSVersion: t.opts.TlsVersion, RTT: time.Since(t0), Err: nil} +} + +func NewTlsPing(ip netip.Addr, host string, port uint16, opts *statute.ScannerOptions) *TlsPing { + return &TlsPing{ + IP: ip, + Host: host, + Port: port, + opts: opts, + } +} + +func (t *TlsPing) errorResult(err error) *TlsPingResult { + r := &TlsPingResult{} + r.Err = err + return r +} + +var ( + _ statute.IPing = (*TlsPing)(nil) + _ statute.IPingResult = (*TlsPingResult)(nil) +) diff --git a/ipscanner/internal/ping/warp.go b/ipscanner/internal/ping/warp.go new file mode 100644 index 000000000..fb165c156 --- /dev/null +++ b/ipscanner/internal/ping/warp.go @@ -0,0 +1,290 @@ +package ping + +import ( + "bytes" + "context" + "crypto/rand" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "math/big" + "net" + "net/netip" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" + "github.com/bepass-org/wireguard-go/warp" + "github.com/flynn/noise" + "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/curve25519" +) + +type WarpPingResult struct { + AddrPort netip.AddrPort + RTT time.Duration + Err error +} + +func (h *WarpPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: h.AddrPort, RTT: h.RTT, CreatedAt: time.Now()} +} + +func (h *WarpPingResult) Error() error { + return h.Err +} + +func (h *WarpPingResult) String() string { + if h.Err != nil { + return fmt.Sprintf("%s", h.Err) + } else { + return fmt.Sprintf("%s: protocol=%s, time=%d ms", h.AddrPort, "warp", h.RTT) + } +} + +type WarpPing struct { + PrivateKey string + PeerPublicKey string + PresharedKey string + IP netip.Addr + + opts statute.ScannerOptions +} + +func (h *WarpPing) Ping() statute.IPingResult { + return h.PingContext(context.Background()) +} + +func (h *WarpPing) PingContext(_ context.Context) statute.IPingResult { + addr := netip.AddrPortFrom(h.IP, warp.RandomWarpPort()) + rtt, err := initiateHandshake( + addr, + h.PrivateKey, + h.PeerPublicKey, + h.PresharedKey, + ) + if err != nil { + return h.errorResult(err) + } + + return &WarpPingResult{AddrPort: addr, RTT: rtt, Err: nil} +} + +func (h *WarpPing) errorResult(err error) *WarpPingResult { + r := &WarpPingResult{} + r.Err = err + return r +} + +func uint32ToBytes(n uint32) []byte { + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, n) + return b +} + +func staticKeypair(privateKeyBase64 string) (noise.DHKey, error) { + privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64) + if err != nil { + return noise.DHKey{}, err + } + + var pubkey, privkey [32]byte + copy(privkey[:], privateKey) + curve25519.ScalarBaseMult(&pubkey, &privkey) + + return noise.DHKey{ + Private: privateKey, + Public: pubkey[:], + }, nil +} + +func ephemeralKeypair() (noise.DHKey, error) { + // Generate an ephemeral private key + ephemeralPrivateKey := make([]byte, 32) + if _, err := rand.Read(ephemeralPrivateKey); err != nil { + return noise.DHKey{}, err + } + + // Derive the corresponding ephemeral public key + ephemeralPublicKey, err := curve25519.X25519(ephemeralPrivateKey, curve25519.Basepoint) + if err != nil { + return noise.DHKey{}, err + } + + return noise.DHKey{ + Private: ephemeralPrivateKey, + Public: ephemeralPublicKey, + }, nil +} + +func randomInt(min, max int) int { + nBig, err := rand.Int(rand.Reader, big.NewInt(int64(max-min+1))) + if err != nil { + panic(err) + } + return int(nBig.Int64()) + min +} + +func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKeyBase64, presharedKeyBase64 string) (time.Duration, error) { + staticKeyPair, err := staticKeypair(privateKeyBase64) + if err != nil { + return 0, err + } + + peerPublicKey, err := base64.StdEncoding.DecodeString(peerPublicKeyBase64) + if err != nil { + return 0, err + } + + presharedKey, err := base64.StdEncoding.DecodeString(presharedKeyBase64) + if err != nil { + return 0, err + } + + if presharedKeyBase64 == "" { + presharedKey = make([]byte, 32) + } + + ephemeral, err := ephemeralKeypair() + if err != nil { + return 0, err + } + + cs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2s) + hs, err := noise.NewHandshakeState(noise.Config{ + CipherSuite: cs, + Pattern: noise.HandshakeIK, + Initiator: true, + StaticKeypair: staticKeyPair, + PeerStatic: peerPublicKey, + Prologue: []byte("WireGuard v1 zx2c4 Jason@zx2c4.com"), + PresharedKey: presharedKey, + PresharedKeyPlacement: 2, + EphemeralKeypair: ephemeral, + Random: rand.Reader, + }) + if err != nil { + return 0, err + } + + // Prepare handshake initiation packet + + // TAI64N timestamp calculation + now := time.Now().UTC() + epochOffset := int64(4611686018427387914) // TAI offset from Unix epoch + + tai64nTimestampBuf := make([]byte, 0, 16) + tai64nTimestampBuf = binary.BigEndian.AppendUint64(tai64nTimestampBuf, uint64(epochOffset+now.Unix())) + tai64nTimestampBuf = binary.BigEndian.AppendUint32(tai64nTimestampBuf, uint32(now.Nanosecond())) + msg, _, _, err := hs.WriteMessage(nil, tai64nTimestampBuf) + if err != nil { + return 0, err + } + + initiationPacket := new(bytes.Buffer) + binary.Write(initiationPacket, binary.BigEndian, []byte{0x01, 0x00, 0x00, 0x00}) + binary.Write(initiationPacket, binary.BigEndian, uint32ToBytes(28)) + binary.Write(initiationPacket, binary.BigEndian, msg) + + macKey := blake2s.Sum256(append([]byte("mac1----"), peerPublicKey...)) + hasher, err := blake2s.New128(macKey[:]) // using macKey as the key + if err != nil { + return 0, err + } + _, err = hasher.Write(initiationPacket.Bytes()) + if err != nil { + return 0, err + } + initiationPacketMAC := hasher.Sum(nil) + + // Append the MAC and 16 null bytes to the initiation packet + binary.Write(initiationPacket, binary.BigEndian, initiationPacketMAC[:16]) + binary.Write(initiationPacket, binary.BigEndian, [16]byte{}) + + conn, err := net.Dial("udp", serverAddr.String()) + if err != nil { + return 0, err + } + defer conn.Close() + + // Generate a random number of packets between 5 and 10 + numPackets := randomInt(1, 2) + for i := 0; i < numPackets; i++ { + // Generate a random packet size between 10 and 40 bytes + packetSize := randomInt(1, 100) + randomPacket := make([]byte, packetSize) + _, err := rand.Read(randomPacket) + if err != nil { + return 0, fmt.Errorf("error generating random packet: %w", err) + } + + // Send the random packet + _, err = conn.Write(randomPacket) + if err != nil { + return 0, fmt.Errorf("error sending random packet: %w", err) + } + + // Wait for a random duration between 200 and 500 milliseconds + time.Sleep(time.Duration(randomInt(200, 500)) * time.Millisecond) + } + + _, err = initiationPacket.WriteTo(conn) + if err != nil { + return 0, err + } + t0 := time.Now() + + response := make([]byte, 92) + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + i, err := conn.Read(response) + if err != nil { + return 0, err + } + rtt := time.Since(t0) + + if i < 60 { + return 0, fmt.Errorf("invalid handshake response length %d bytes", i) + } + + // Check the response type + if response[0] != 2 { // 2 is the message type for response + return 0, errors.New("invalid response type") + } + + // Extract sender and receiver index from the response + // peer index + _ = binary.LittleEndian.Uint32(response[4:8]) + // our index(we set it to 28) + ourIndex := binary.LittleEndian.Uint32(response[8:12]) + if ourIndex != 28 { // Check if the response corresponds to our sender index + return 0, errors.New("invalid sender index in response") + } + + payload, _, _, err := hs.ReadMessage(nil, response[12:60]) + if err != nil { + return 0, err + } + + // Check if the payload is empty (as expected in WireGuard handshake) + if len(payload) != 0 { + return 0, errors.New("unexpected payload in response") + } + + return rtt, nil +} + +func NewWarpPing(ip netip.Addr, opts *statute.ScannerOptions) *WarpPing { + return &WarpPing{ + PrivateKey: opts.WarpPrivateKey, + PeerPublicKey: opts.WarpPeerPublicKey, + PresharedKey: opts.WarpPresharedKey, + IP: ip, + + opts: *opts, + } +} + +var ( + _ statute.IPing = (*WarpPing)(nil) + _ statute.IPingResult = (*WarpPingResult)(nil) +) diff --git a/ipscanner/internal/statute/default.go b/ipscanner/internal/statute/default.go new file mode 100644 index 000000000..24265a4a4 --- /dev/null +++ b/ipscanner/internal/statute/default.go @@ -0,0 +1,195 @@ +package statute + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "net/netip" + "time" + + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" +) + +var FinalOptions *ScannerOptions + +func DefaultHTTPClientFunc(rawDialer TDialerFunc, tlsDialer TDialerFunc, quicDialer TQuicDialerFunc, targetAddr ...string) *http.Client { + var defaultDialer TDialerFunc + if rawDialer == nil { + defaultDialer = DefaultDialerFunc + } else { + defaultDialer = rawDialer + } + var defaultTLSDialer TDialerFunc + if rawDialer == nil { + defaultTLSDialer = DefaultTLSDialerFunc + } else { + defaultTLSDialer = tlsDialer + } + var defaultQuicDialer TQuicDialerFunc + if quicDialer == nil { + defaultQuicDialer = DefaultQuicDialerFunc + } else { + defaultQuicDialer = quicDialer + } + + var transport http.RoundTripper + if FinalOptions.UseHTTP3 { + transport = &http3.RoundTripper{ + DisableCompression: FinalOptions.DisableCompression, + Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + dest := addr + if len(targetAddr) > 0 { + dest = targetAddr[0] + } + return defaultQuicDialer(ctx, dest, tlsCfg, cfg) + }, + } + } else { + trans := &http.Transport{ + DialContext: defaultDialer, + DialTLSContext: defaultTLSDialer, + ForceAttemptHTTP2: FinalOptions.UseHTTP2, + DisableCompression: FinalOptions.DisableCompression, + MaxIdleConnsPerHost: -1, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: FinalOptions.InsecureSkipVerify, + ServerName: FinalOptions.Hostname, + }, + } + transport = trans + } + + return &http.Client{ + Transport: transport, + Timeout: FinalOptions.ConnectionTimeout, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } +} + +func DefaultDialerFunc(ctx context.Context, network, addr string) (net.Conn, error) { + fmt.Println(addr) + d := &net.Dialer{ + Timeout: FinalOptions.ConnectionTimeout, // Connection timeout + // Add other custom settings as needed + } + return d.DialContext(ctx, network, addr) +} + +func getServerName(address string) (string, error) { + host, _, err := net.SplitHostPort(address) + if err != nil { + return "", err // handle the error properly in your real application + } + return host, nil +} + +func defaultTLSConfig(addr string) *tls.Config { + allowInsecure := false + sni, err := getServerName(addr) + if err != nil { + allowInsecure = true + } + + if FinalOptions.Hostname != "" { + sni = FinalOptions.Hostname + } + + alpnProtocols := []string{"http/1.1"} + + // Add protocols based on flags + if FinalOptions.UseHTTP3 { + alpnProtocols = []string{"http/1.1"} // ALPN token for HTTP/3 + } + if FinalOptions.UseHTTP2 { + alpnProtocols = []string{"h2", "http/1.1"} // ALPN token for HTTP/2 + } + + // Initiate a TLS handshake over the connection + return &tls.Config{ + InsecureSkipVerify: allowInsecure || FinalOptions.InsecureSkipVerify, + ServerName: sni, + MinVersion: FinalOptions.TlsVersion, + MaxVersion: FinalOptions.TlsVersion, + NextProtos: alpnProtocols, + } +} + +// DefaultTLSDialerFunc is a custom TLS dialer function +func DefaultTLSDialerFunc(ctx context.Context, network, addr string) (net.Conn, error) { + // Dial the raw connection using the default dialer + rawConn, err := DefaultDialerFunc(ctx, network, addr) + if err != nil { + return nil, err + } + + // Ensure the raw connection is closed in case of an error after this point + defer func() { + if err != nil { + _ = rawConn.Close() + } + }() + + // Prepare the TLS client connection + tlsClientConn := tls.Client(rawConn, defaultTLSConfig(addr)) + + // Perform the handshake with a timeout + err = tlsClientConn.SetDeadline(time.Now().Add(FinalOptions.HandshakeTimeout)) + if err != nil { + return nil, err + } + + err = tlsClientConn.Handshake() + if err != nil { + return nil, err // rawConn will be closed by the deferred function + } + + // Reset the deadline for future I/O operations + err = tlsClientConn.SetDeadline(time.Time{}) + if err != nil { + return nil, err + } + + // Return the established TLS connection + // Cancel the deferred closure of rawConn since everything succeeded + err = nil + return tlsClientConn, nil +} + +func DefaultQuicDialerFunc(ctx context.Context, addr string, _ *tls.Config, _ *quic.Config) (quic.EarlyConnection, error) { + quicConfig := &quic.Config{ + MaxIdleTimeout: FinalOptions.ConnectionTimeout, + HandshakeIdleTimeout: FinalOptions.HandshakeTimeout, + } + return quic.DialAddrEarly(ctx, addr, defaultTLSConfig(addr), quicConfig) +} + +func DefaultCFRanges() []netip.Prefix { + return []netip.Prefix{ + netip.MustParsePrefix("103.21.244.0/22"), + netip.MustParsePrefix("103.22.200.0/22"), + netip.MustParsePrefix("103.31.4.0/22"), + netip.MustParsePrefix("104.16.0.0/12"), + netip.MustParsePrefix("108.162.192.0/18"), + netip.MustParsePrefix("131.0.72.0/22"), + netip.MustParsePrefix("141.101.64.0/18"), + netip.MustParsePrefix("162.158.0.0/15"), + netip.MustParsePrefix("172.64.0.0/13"), + netip.MustParsePrefix("173.245.48.0/20"), + netip.MustParsePrefix("188.114.96.0/20"), + netip.MustParsePrefix("190.93.240.0/20"), + netip.MustParsePrefix("197.234.240.0/22"), + netip.MustParsePrefix("198.41.128.0/17"), + netip.MustParsePrefix("2400:cb00::/32"), + netip.MustParsePrefix("2405:8100::/32"), + netip.MustParsePrefix("2405:b500::/32"), + netip.MustParsePrefix("2606:4700::/32"), + netip.MustParsePrefix("2803:f800::/32"), + netip.MustParsePrefix("2c0f:f248::/32"), + netip.MustParsePrefix("2a06:98c0::/29"), + } +} diff --git a/ipscanner/internal/statute/ping.go b/ipscanner/internal/statute/ping.go new file mode 100644 index 000000000..816a26fc6 --- /dev/null +++ b/ipscanner/internal/statute/ping.go @@ -0,0 +1,35 @@ +package statute + +import ( + "context" + "crypto/tls" + "fmt" +) + +type IPingResult interface { + Result() IPInfo + Error() error + fmt.Stringer +} + +type IPing interface { + Ping() IPingResult + PingContext(context.Context) IPingResult +} + +func TlsVersionToString(ver uint16) string { + switch ver { + case tls.VersionSSL30: + return "SSL 3.0" + case tls.VersionTLS10: + return "TLS 1.0" + case tls.VersionTLS11: + return "TLS 1.1" + case tls.VersionTLS12: + return "TLS 1.2" + case tls.VersionTLS13: + return "TLS 1.3" + default: + return "unknown" + } +} diff --git a/ipscanner/internal/statute/queue.go b/ipscanner/internal/statute/queue.go new file mode 100644 index 000000000..16975362a --- /dev/null +++ b/ipscanner/internal/statute/queue.go @@ -0,0 +1,34 @@ +package statute + +import ( + "sort" + "time" +) + +type IPInfQueue struct { + items []IPInfo +} + +// Enqueue adds an item and then sorts the queue. +func (q *IPInfQueue) Enqueue(item IPInfo) { + q.items = append(q.items, item) + sort.Slice(q.items, func(i, j int) bool { + return q.items[i].RTT < q.items[j].RTT + }) +} + +// Dequeue removes and returns the item with the lowest RTT. +func (q *IPInfQueue) Dequeue() IPInfo { + if len(q.items) == 0 { + return IPInfo{} // Returning an empty IPInfo when the queue is empty. + } + item := q.items[0] + q.items = q.items[1:] + item.CreatedAt = time.Now() + return item +} + +// Size returns the number of items in the queue. +func (q *IPInfQueue) Size() int { + return len(q.items) +} diff --git a/ipscanner/internal/statute/statute.go b/ipscanner/internal/statute/statute.go new file mode 100644 index 000000000..4efe9ceeb --- /dev/null +++ b/ipscanner/internal/statute/statute.go @@ -0,0 +1,66 @@ +package statute + +import ( + "context" + "crypto/tls" + "log/slog" + "net" + "net/http" + "net/netip" + "time" + + "github.com/quic-go/quic-go" +) + +type TIPQueueChangeCallback func(ips []IPInfo) + +type ( + TDialerFunc func(ctx context.Context, network, addr string) (net.Conn, error) + TQuicDialerFunc func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) + THTTPClientFunc func(rawDialer TDialerFunc, tlsDialer TDialerFunc, quicDialer TQuicDialerFunc, targetAddr ...string) *http.Client +) + +var ( + HTTPPing = 1 << 1 + TLSPing = 1 << 2 + TCPPing = 1 << 3 + QUICPing = 1 << 4 + WARPPing = 1 << 5 +) + +type IPInfo struct { + AddrPort netip.AddrPort + RTT time.Duration + CreatedAt time.Time +} + +type ScannerOptions struct { + UseIPv4 bool + UseIPv6 bool + CidrList []netip.Prefix // CIDR ranges to scan + SelectedOps int + Logger *slog.Logger + InsecureSkipVerify bool + RawDialerFunc TDialerFunc + TLSDialerFunc TDialerFunc + QuicDialerFunc TQuicDialerFunc + HttpClientFunc THTTPClientFunc + UseHTTP3 bool + UseHTTP2 bool + DisableCompression bool + HTTPPath string + Referrer string + UserAgent string + Hostname string + WarpPrivateKey string + WarpPeerPublicKey string + WarpPresharedKey string + Port uint16 + IPQueueSize int + IPQueueTTL time.Duration + MaxDesirableRTT time.Duration + IPQueueChangeCallback TIPQueueChangeCallback + ConnectionTimeout time.Duration + HandshakeTimeout time.Duration + TlsVersion uint16 +} diff --git a/ipscanner/scanner.go b/ipscanner/scanner.go new file mode 100644 index 000000000..04700fde4 --- /dev/null +++ b/ipscanner/scanner.go @@ -0,0 +1,270 @@ +package ipscanner + +import ( + "context" + "crypto/tls" + "log/slog" + "net/netip" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/engine" + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type IPScanner struct { + options statute.ScannerOptions + log *slog.Logger + engine *engine.Engine +} + +func NewScanner(options ...Option) *IPScanner { + p := &IPScanner{ + options: statute.ScannerOptions{ + UseIPv4: true, + UseIPv6: true, + CidrList: statute.DefaultCFRanges(), + SelectedOps: 0, + Logger: slog.Default(), + InsecureSkipVerify: true, + RawDialerFunc: statute.DefaultDialerFunc, + TLSDialerFunc: statute.DefaultTLSDialerFunc, + QuicDialerFunc: statute.DefaultQuicDialerFunc, + HttpClientFunc: statute.DefaultHTTPClientFunc, + UseHTTP3: false, + UseHTTP2: false, + DisableCompression: false, + HTTPPath: "/", + Referrer: "", + UserAgent: "Chrome/80.0.3987.149", + Hostname: "www.cloudflare.com", + WarpPresharedKey: "", + WarpPeerPublicKey: "", + WarpPrivateKey: "", + Port: 443, + IPQueueSize: 8, + MaxDesirableRTT: 400 * time.Millisecond, + IPQueueTTL: 30 * time.Second, + ConnectionTimeout: 1 * time.Second, + HandshakeTimeout: 1 * time.Second, + TlsVersion: tls.VersionTLS13, + }, + log: slog.Default(), + } + + for _, option := range options { + option(p) + } + + return p +} + +type Option func(*IPScanner) + +func WithUseIPv4(useIPv4 bool) Option { + return func(i *IPScanner) { + i.options.UseIPv4 = useIPv4 + } +} + +func WithUseIPv6(useIPv6 bool) Option { + return func(i *IPScanner) { + i.options.UseIPv6 = useIPv6 + } +} + +func WithDialer(d statute.TDialerFunc) Option { + return func(i *IPScanner) { + i.options.RawDialerFunc = d + } +} + +func WithTLSDialer(t statute.TDialerFunc) Option { + return func(i *IPScanner) { + i.options.TLSDialerFunc = t + } +} + +func WithQuicDialer(q statute.TQuicDialerFunc) Option { + return func(i *IPScanner) { + i.options.QuicDialerFunc = q + } +} + +func WithHttpClientFunc(h statute.THTTPClientFunc) Option { + return func(i *IPScanner) { + i.options.HttpClientFunc = h + } +} + +func WithUseHTTP3(useHTTP3 bool) Option { + return func(i *IPScanner) { + i.options.UseHTTP3 = useHTTP3 + } +} + +func WithUseHTTP2(useHTTP2 bool) Option { + return func(i *IPScanner) { + i.options.UseHTTP2 = useHTTP2 + } +} + +func WithDisableCompression(disableCompression bool) Option { + return func(i *IPScanner) { + i.options.DisableCompression = disableCompression + } +} + +func WithHttpPath(path string) Option { + return func(i *IPScanner) { + i.options.HTTPPath = path + } +} + +func WithReferrer(referrer string) Option { + return func(i *IPScanner) { + i.options.Referrer = referrer + } +} + +func WithUserAgent(userAgent string) Option { + return func(i *IPScanner) { + i.options.UserAgent = userAgent + } +} + +func WithLogger(logger *slog.Logger) Option { + return func(i *IPScanner) { + i.log = logger + i.options.Logger = logger + } +} + +func WithInsecureSkipVerify(insecureSkipVerify bool) Option { + return func(i *IPScanner) { + i.options.InsecureSkipVerify = insecureSkipVerify + } +} + +func WithHostname(hostname string) Option { + return func(i *IPScanner) { + i.options.Hostname = hostname + } +} + +func WithPort(port uint16) Option { + return func(i *IPScanner) { + i.options.Port = port + } +} + +func WithCidrList(cidrList []netip.Prefix) Option { + return func(i *IPScanner) { + i.options.CidrList = cidrList + } +} + +func WithHTTPPing() Option { + return func(i *IPScanner) { + i.options.SelectedOps |= statute.HTTPPing + } +} + +func WithWarpPing() Option { + return func(i *IPScanner) { + i.options.SelectedOps |= statute.WARPPing + } +} + +func WithQUICPing() Option { + return func(i *IPScanner) { + i.options.SelectedOps |= statute.QUICPing + } +} + +func WithTCPPing() Option { + return func(i *IPScanner) { + i.options.SelectedOps |= statute.TCPPing + } +} + +func WithTLSPing() Option { + return func(i *IPScanner) { + i.options.SelectedOps |= statute.TLSPing + } +} + +func WithIPQueueSize(size int) Option { + return func(i *IPScanner) { + i.options.IPQueueSize = size + } +} + +func WithMaxDesirableRTT(threshold time.Duration) Option { + return func(i *IPScanner) { + i.options.MaxDesirableRTT = threshold + } +} + +func WithIPQueueTTL(ttl time.Duration) Option { + return func(i *IPScanner) { + i.options.IPQueueTTL = ttl + } +} + +func WithConnectionTimeout(timeout time.Duration) Option { + return func(i *IPScanner) { + i.options.ConnectionTimeout = timeout + } +} + +func WithHandshakeTimeout(timeout time.Duration) Option { + return func(i *IPScanner) { + i.options.HandshakeTimeout = timeout + } +} + +func WithTlsVersion(version uint16) Option { + return func(i *IPScanner) { + i.options.TlsVersion = version + } +} + +func WithWarpPrivateKey(privateKey string) Option { + return func(i *IPScanner) { + i.options.WarpPrivateKey = privateKey + } +} + +func WithWarpPeerPublicKey(peerPublicKey string) Option { + return func(i *IPScanner) { + i.options.WarpPeerPublicKey = peerPublicKey + } +} + +func WithWarpPreSharedKey(presharedKey string) Option { + return func(i *IPScanner) { + i.options.WarpPresharedKey = presharedKey + } +} + +// run engine and in case of new event call onChange callback also if it gets canceled with context +// cancel all operations + +func (i *IPScanner) Run(ctx context.Context) { + statute.FinalOptions = &i.options + if !i.options.UseIPv4 && !i.options.UseIPv6 { + i.log.Error("Fatal: both IPv4 and IPv6 are disabled, nothing to do") + return + } + i.engine = engine.NewScannerEngine(&i.options) + go i.engine.Run(ctx) +} + +func (i *IPScanner) GetAvailableIPs() []statute.IPInfo { + if i.engine != nil { + return i.engine.GetAvailableIPs(false) + } + return nil +} + +type IPInfo = statute.IPInfo diff --git a/iputils/iputils.go b/iputils/iputils.go new file mode 100644 index 000000000..6633881d1 --- /dev/null +++ b/iputils/iputils.go @@ -0,0 +1,94 @@ +package iputils + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "net/netip" + "time" +) + +// RandomIPFromPrefix returns a random IP from the provided CIDR prefix. +// Supports IPv4 and IPv6. Does not support mapped inputs. +func RandomIPFromPrefix(cidr netip.Prefix) (netip.Addr, error) { + startingAddress := cidr.Masked().Addr() + if startingAddress.Is4In6() { + return netip.Addr{}, errors.New("mapped v4 addresses not supported") + } + + prefixLen := cidr.Bits() + if prefixLen == -1 { + return netip.Addr{}, fmt.Errorf("invalid cidr: %s", cidr) + } + + // Initialise rand number generator + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + + // Find the bit length of the Host portion of the provided CIDR + // prefix + hostLen := big.NewInt(int64(startingAddress.BitLen() - prefixLen)) + + // Find the max value for our random number + max := new(big.Int).Exp(big.NewInt(2), hostLen, nil) + + // Generate the random number + randInt := new(big.Int).Rand(rng, max) + + // Get the first address in the CIDR prefix in 16-bytes form + startingAddress16 := startingAddress.As16() + + // Convert the first address into a decimal number + startingAddressInt := new(big.Int).SetBytes(startingAddress16[:]) + + // Add the random number to the decimal form of the starting address + // to get a random address in the desired range + randomAddressInt := new(big.Int).Add(startingAddressInt, randInt) + + // Convert the random address from decimal form back into netip.Addr + randomAddress, ok := netip.AddrFromSlice(randomAddressInt.FillBytes(make([]byte, 16))) + if !ok { + return netip.Addr{}, fmt.Errorf("failed to generate random IP from CIDR: %s", cidr) + } + + // Unmap any mapped v4 addresses before return + return randomAddress.Unmap(), nil +} + +// func ParseResolveAddressPort(hostname string) (netip.AddrPort, error) { +// // Attempt to split the hostname into a host and port +// host, port, err := net.SplitHostPort(hostname) +// if err != nil { +// return netip.AddrPort{}, fmt.Errorf("can't parse provided hostname into host and port: %w", err) +// } + +// // Convert the string port to a uint16 +// portInt, err := strconv.Atoi(port) +// if err != nil { +// return netip.AddrPort{}, fmt.Errorf("error parsing port: %w", err) +// } + +// if portInt < 1 || portInt > 65535 { +// return netip.AddrPort{}, fmt.Errorf("port number %d is out of range", portInt) +// } + +// // Attempt to parse the host into an IP. Return on success. +// addr, err := netip.ParseAddr(host) +// if err == nil { +// return netip.AddrPortFrom(addr.Unmap(), uint16(portInt)), nil +// } + +// // If the host wasn't an IP, perform a lookup +// ips, err := net.LookupIP(host) +// if err != nil { +// return netip.AddrPort{}, fmt.Errorf("hostname lookup failed: %w", err) +// } + +// // Take the first IP and then return it +// addr, ok := netip.AddrFromSlice(ips[0]) +// if !ok { +// return netip.AddrPort{}, errors.New("failed to parse ip") +// } + +// return netip.AddrPortFrom(addr.Unmap(), uint16(portInt)), nil +// } diff --git a/psiphon/p.go b/psiphon/p.go index bdc6e7c37..4ea7fff64 100644 --- a/psiphon/p.go +++ b/psiphon/p.go @@ -104,8 +104,8 @@ func StartTunnel( embeddedServerEntryList string, params Parameters, paramsDelta ParametersDelta, - noticeReceiver func(NoticeEvent)) (retTunnel *Tunnel, retErr error) { - + noticeReceiver func(NoticeEvent), +) (retTunnel *Tunnel, retErr error) { config, err := psiphon.LoadConfig(configJSON) if err != nil { return nil, errors.New("failed to load config file") @@ -369,11 +369,11 @@ func RunPsiphon(wgBind, localSocksPort, country string, ctx context.Context) err select { case <-ctx.Done(): internalCtx.Done() - return fmt.Errorf("psiphon handshake operation canceled by user") + return errors.New("psiphon handshake operation canceled by user") case <-timeoutTimer.C: // Handle the internal timeout internalCtx.Done() - return fmt.Errorf("psiphon handshake maximum time exceeded") + return errors.New("psiphon handshake maximum time exceeded") default: tunnel, err = StartTunnel(internalCtx, []byte(configJSON), "", p, nil, nil) if err == nil { diff --git a/psiphon/relay.go b/psiphon/relay.go index b3b4f18f4..0b9b77442 100644 --- a/psiphon/relay.go +++ b/psiphon/relay.go @@ -46,18 +46,18 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { func sendUdpPacket(remoteHost, remotePort, packetHex string) (string, error) { packet, err := hex.DecodeString(packetHex) if err != nil { - return "", fmt.Errorf("invalid hex string: %v", err) + return "", fmt.Errorf("invalid hex string: %w", err) } remoteAddr := net.JoinHostPort(remoteHost, remotePort) conn, err := net.Dial("udp", remoteAddr) if err != nil { - return "", fmt.Errorf("dial error: %v", err) + return "", fmt.Errorf("dial error: %w", err) } defer conn.Close() if _, err = conn.Write(packet); err != nil { - return "", fmt.Errorf("write error: %v", err) + return "", fmt.Errorf("write error: %w", err) } conn.SetReadDeadline(time.Now().Add(5 * time.Second)) @@ -65,7 +65,7 @@ func sendUdpPacket(remoteHost, remotePort, packetHex string) (string, error) { buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { - return "", fmt.Errorf("read error: %v", err) + return "", fmt.Errorf("read error: %w", err) } return hex.EncodeToString(buffer[:n]), nil diff --git a/warp/account.go b/warp/account.go index e02b55872..7a9a26389 100644 --- a/warp/account.go +++ b/warp/account.go @@ -30,8 +30,10 @@ var ( dc = 0 ) -var defaultHeaders = makeDefaultHeaders() -var client = makeClient() +var ( + defaultHeaders = makeDefaultHeaders() + client = makeClient() +) type AccountData struct { AccountID string `json:"account_id"` @@ -281,7 +283,6 @@ func enableWarp(accountData *AccountData) error { } func getServerConf(accountData *AccountData) (*ConfigurationData, error) { - req, err := http.NewRequest("GET", getConfigURL(accountData.AccountID), nil) if err != nil { return nil, err @@ -347,7 +348,6 @@ func getServerConf(accountData *AccountData) (*ConfigurationData, error) { } func updateLicenseKey(accountData *AccountData, confData *ConfigurationData) (bool, error) { - if confData.AccountType == "free" && accountData.LicenseKey != "" { data := map[string]interface{}{ @@ -400,7 +400,6 @@ func updateLicenseKey(accountData *AccountData, confData *ConfigurationData) (bo } func getDeviceActive(accountData *AccountData) (bool, error) { - req, err := http.NewRequest("GET", getDevicesURL(accountData.AccountID), nil) if err != nil { return false, err @@ -440,7 +439,6 @@ func getDeviceActive(accountData *AccountData) (bool, error) { } func setDeviceActive(accountData *AccountData, status bool) (bool, error) { - data := map[string]interface{}{ "active": status, } @@ -487,7 +485,6 @@ func setDeviceActive(accountData *AccountData, status bool) (bool, error) { } func getWireguardConfig(privateKey, address1, address2, publicKey, endpoint string) string { - var buffer bytes.Buffer buffer.WriteString("[Interface]\n") @@ -507,11 +504,10 @@ func getWireguardConfig(privateKey, address1, address2, publicKey, endpoint stri } func createConf(accountData *AccountData, confData *ConfigurationData) error { - config := getWireguardConfig(accountData.PrivateKey, confData.LocalAddressIPv4, confData.LocalAddressIPv6, confData.EndpointPublicKey, confData.EndpointAddressHost) - return os.WriteFile(profileFile, []byte(config), 0600) + return os.WriteFile(profileFile, []byte(config), 0o600) } func LoadOrCreateIdentity(license string) error { @@ -582,7 +578,7 @@ func LoadOrCreateIdentity(license string) error { fmt.Println("Creating WireGuard configuration...") err = createConf(accountData, confData) if err != nil { - return fmt.Errorf("unable to enable write config file, Error: %v", err.Error()) + return fmt.Errorf("unable to enable write config file: %w", err) } fmt.Println("All done! Find your files here:") @@ -597,6 +593,7 @@ func fileExist(f string) bool { } return true } + func removeFile(f string) { if fileExist(f) { e := os.Remove(f) @@ -635,7 +632,6 @@ func CheckProfileExists(license string) bool { } func RemoveDevice(account AccountData) error { - headers := map[string]string{ "Content-Type": "application/json", "User-Agent": "okhttp/3.12.1", diff --git a/warp/endpoint.go b/warp/endpoint.go index f2e11af53..69cfcf13d 100644 --- a/warp/endpoint.go +++ b/warp/endpoint.go @@ -4,6 +4,8 @@ import ( "math/rand" "net/netip" "time" + + "github.com/bepass-org/wireguard-go/iputils" ) func WarpPrefixes() []netip.Prefix { @@ -20,24 +22,82 @@ func WarpPrefixes() []netip.Prefix { } } -func RandomWarpEndpoint() (netip.AddrPort, error) { - ports := []int{500, 854, 859, 864, 878, 880, 890, 891, 894, 903, 908, 928, 934, 939, 942, - 943, 945, 946, 955, 968, 987, 988, 1002, 1010, 1014, 1018, 1070, 1074, 1180, 1387, 1701, - 1843, 2371, 2408, 2506, 3138, 3476, 3581, 3854, 4177, 4198, 4233, 4500, 5279, - 5956, 7103, 7152, 7156, 7281, 7559, 8319, 8742, 8854, 8886} - - // Seed the random number generator +func RandomWarpPrefix() netip.Prefix { + cidrs := WarpPrefixes() rng := rand.New(rand.NewSource(time.Now().UnixNano())) + return cidrs[rng.Intn(len(cidrs))] +} - // Pick a random port number - randomPort := uint16(ports[rng.Intn(len(ports))]) +func WarpPorts() []uint16 { + return []uint16{ + 500, + 854, + 859, + 864, + 878, + 880, + 890, + 891, + 894, + 903, + 908, + 928, + 934, + 939, + 942, + 943, + 945, + 946, + 955, + 968, + 987, + 988, + 1002, + 1010, + 1014, + 1018, + 1070, + 1074, + 1180, + 1387, + 1701, + 1843, + 2371, + 2408, + 2506, + 3138, + 3476, + 3581, + 3854, + 4177, + 4198, + 4233, + 4500, + 5279, + 5956, + 7103, + 7152, + 7156, + 7281, + 7559, + 8319, + 8742, + 8854, + 8886, + } +} - cidrs := WarpPrefixes() +func RandomWarpPort() uint16 { + ports := WarpPorts() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + return ports[rng.Intn(len(ports))] +} - randomIP, err := RandomIPFromPrefix(cidrs[rng.Intn(len(cidrs))]) +func RandomWarpEndpoint() (netip.AddrPort, error) { + randomIP, err := iputils.RandomIPFromPrefix(RandomWarpPrefix()) if err != nil { return netip.AddrPort{}, err } - return netip.AddrPortFrom(randomIP, randomPort), nil + return netip.AddrPortFrom(randomIP, RandomWarpPort()), nil } diff --git a/warp/key.go b/warp/key.go index 54e3f4d10..e8bf0a688 100644 --- a/warp/key.go +++ b/warp/key.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/base64" "fmt" + "golang.org/x/crypto/curve25519" ) @@ -23,7 +24,7 @@ type Key [KeyLen]byte func GenerateKey() (Key, error) { b := make([]byte, KeyLen) if _, err := rand.Read(b); err != nil { - return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err) + return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %w", err) } return NewKey(b) diff --git a/warp/tls.go b/warp/tls.go index 324620321..efcd9d74c 100644 --- a/warp/tls.go +++ b/warp/tls.go @@ -1,21 +1,18 @@ package warp import ( - "errors" "fmt" "io" - "math/big" - "math/rand" "net" "net/netip" - "time" + + "github.com/bepass-org/wireguard-go/iputils" tls "github.com/refraction-networking/utls" ) // Dialer is a struct that holds various options for custom dialing. -type Dialer struct { -} +type Dialer struct{} const ( extensionServerName uint16 = 0x0 @@ -129,18 +126,21 @@ func (d *Dialer) makeTLSHelloPacketWithSNICurve(plainConn net.Conn, config *tls. &tls.SupportedPointsExtension{SupportedPoints: []byte{0}}, // uncompressed &tls.SessionTicketExtension{}, &tls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}, - &tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{ - tls.ECDSAWithP256AndSHA256, - tls.ECDSAWithP384AndSHA384, - tls.ECDSAWithP521AndSHA512, - tls.PSSWithSHA256, - tls.PSSWithSHA384, - tls.PSSWithSHA512, - tls.PKCS1WithSHA256, - tls.PKCS1WithSHA384, - tls.PKCS1WithSHA512, - tls.ECDSAWithSHA1, - tls.PKCS1WithSHA1}}, + &tls.SignatureAlgorithmsExtension{ + SupportedSignatureAlgorithms: []tls.SignatureScheme{ + tls.ECDSAWithP256AndSHA256, + tls.ECDSAWithP384AndSHA384, + tls.ECDSAWithP521AndSHA512, + tls.PSSWithSHA256, + tls.PSSWithSHA384, + tls.PSSWithSHA512, + tls.PKCS1WithSHA256, + tls.PKCS1WithSHA384, + tls.PKCS1WithSHA512, + tls.ECDSAWithSHA1, + tls.PKCS1WithSHA1, + }, + }, &tls.KeyShareExtension{KeyShares: []tls.KeyShare{ {Group: tls.CurveID(tls.GREASE_PLACEHOLDER), Data: []byte{0}}, {Group: tls.X25519}, @@ -153,73 +153,25 @@ func (d *Dialer) makeTLSHelloPacketWithSNICurve(plainConn net.Conn, config *tls. GetSessionID: nil, } err := utlsConn.ApplyPreset(&spec) - if err != nil { - return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err) + return nil, fmt.Errorf("uTlsConn.Handshake() error: %w", err) } err = utlsConn.Handshake() - if err != nil { - return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err) + return nil, fmt.Errorf("uTlsConn.Handshake() error: %w", err) } return utlsConn, nil } -// RandomIPFromPrefix returns a random IP from the provided CIDR prefix. -// Supports IPv4 and IPv6. Does not support mapped inputs. -func RandomIPFromPrefix(cidr netip.Prefix) (netip.Addr, error) { - startingAddress := cidr.Masked().Addr() - if startingAddress.Is4In6() { - return netip.Addr{}, errors.New("mapped v4 addresses not supported") - } - - prefixLen := cidr.Bits() - if prefixLen == -1 { - return netip.Addr{}, fmt.Errorf("invalid cidr: %s", cidr) - } - - // Initialise rand number generator - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - - // Find the bit length of the Host portion of the provided CIDR - // prefix - hostLen := big.NewInt(int64(startingAddress.BitLen() - prefixLen)) - - // Find the max value for our random number - max := new(big.Int).Exp(big.NewInt(2), hostLen, nil) - - // Generate the random number - randInt := new(big.Int).Rand(rng, max) - - // Get the first address in the CIDR prefix in 16-bytes form - startingAddress16 := startingAddress.As16() - - // Convert the first address into a decimal number - startingAddressInt := new(big.Int).SetBytes(startingAddress16[:]) - - // Add the random number to the decimal form of the starting address - // to get a random address in the desired range - randomAddressInt := new(big.Int).Add(startingAddressInt, randInt) - - // Convert the random address from decimal form back into netip.Addr - randomAddress, ok := netip.AddrFromSlice(randomAddressInt.FillBytes(make([]byte, 16))) - if !ok { - return netip.Addr{}, fmt.Errorf("failed to generate random IP from CIDR: %s", cidr) - } - - // Unmap any mapped v4 addresses before return - return randomAddress.Unmap(), nil -} - // TLSDial dials a TLS connection. func (d *Dialer) TLSDial(plainDialer *net.Dialer, network, addr string) (net.Conn, error) { sni, _, err := net.SplitHostPort(addr) if err != nil { return nil, err } - ip, err := RandomIPFromPrefix(netip.MustParsePrefix("141.101.113.0/24")) + ip, err := iputils.RandomIPFromPrefix(netip.MustParsePrefix("141.101.113.0/24")) if err != nil { return nil, err } diff --git a/wiresocks/config.go b/wiresocks/config.go index 1a3a27f9e..0ddff54d8 100644 --- a/wiresocks/config.go +++ b/wiresocks/config.go @@ -4,207 +4,132 @@ import ( "encoding/base64" "encoding/hex" "errors" - "strings" + "fmt" + "net/netip" "github.com/go-ini/ini" - - "net/netip" ) 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 "", errors.New(keyName + " should not be empty") - } - 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) { decoded, err := base64.StdEncoding.DecodeString(key) if err != nil { - return "", errors.New("invalid base64 string: " + key) + return "", fmt.Errorf("invalid base64 string: %s", key) } if len(decoded) != 32 { - return "", errors.New("key should be 32 bytes: " + key) + return "", fmt.Errorf("key should be 32 bytes: %s", key) } 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 + return InterfaceConfig{}, errors.New("PrivateKey should not be empty") } - 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 - } - - 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/scanner.go b/wiresocks/scanner.go index 61e41141b..5a47dcf86 100644 --- a/wiresocks/scanner.go +++ b/wiresocks/scanner.go @@ -2,14 +2,13 @@ package wiresocks import ( "context" - "crypto/rand" + "errors" "fmt" "net" "net/netip" - "strings" "time" - "github.com/bepass-org/ipscanner" + "github.com/bepass-org/wireguard-go/ipscanner" "github.com/bepass-org/wireguard-go/warp" "github.com/go-ini/ini" ) @@ -27,7 +26,7 @@ func canConnectIPv6(remoteAddr netip.AddrPort) bool { return true } -func RunScan(ctx context.Context, rtt time.Duration) (result []string, err error) { +func RunScan(ctx context.Context, rtt time.Duration) (result []ipscanner.IPInfo, err error) { cfg, err := ini.Load("./primary/wgcf-profile.ini") if err != nil { return nil, fmt.Errorf("failed to read file: %w", err) @@ -39,13 +38,6 @@ func RunScan(ctx context.Context, rtt time.Duration) (result []string, err error // Reading the public key from the 'Peer' section publicKey := cfg.Section("Peer").Key("PublicKey").String() - // TODO: ipscanner doesn't support netip.Prefix yet - prefixes := warp.WarpPrefixes() - stringedPrefixes := make([]string, len(prefixes)) - for i, p := range prefixes { - stringedPrefixes[i] = p.String() - } - // new scanner scanner := ipscanner.NewScanner( ipscanner.WithWarpPing(), @@ -53,56 +45,34 @@ func RunScan(ctx context.Context, rtt time.Duration) (result []string, err error ipscanner.WithWarpPeerPublicKey(publicKey), ipscanner.WithUseIPv6(canConnectIPv6(netip.MustParseAddrPort("[2001:4860:4860::8888]:80"))), ipscanner.WithUseIPv4(true), - ipscanner.WithMaxDesirableRTT(int(rtt.Milliseconds())), - ipscanner.WithCidrList(stringedPrefixes), + ipscanner.WithMaxDesirableRTT(rtt), + ipscanner.WithCidrList(warp.WarpPrefixes()), ) - scanner.Run() - timeoutTimer := time.NewTimer(2 * time.Minute) - defer timeoutTimer.Stop() + ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) + defer cancel() + + scanner.Run(ctx) + + t := time.NewTicker(1 * time.Second) + defer t.Stop() for { + ipList := scanner.GetAvailableIPs() + if len(ipList) > 1 { + for i := 0; i < 2; i++ { + result = append(result, ipList[i]) + } + return result, nil + } + select { case <-ctx.Done(): // Context is done - canceled externally - scanner.Stop() - return nil, fmt.Errorf("user canceled the operation") - case <-timeoutTimer.C: - // Handle the internal timeout - scanner.Stop() - return nil, fmt.Errorf("scanner maximum time exceeded") - default: - ipList := scanner.GetAvailableIPS() - if len(ipList) > 1 { - scanner.Stop() - for i := 0; i < 2; i++ { - result = append(result, ipToAddress(ipList[i])) - } - return result, nil - } - time.Sleep(1 * time.Second) // Prevent the loop from spinning too fast + return nil, errors.New("user canceled the operation") + case <-t.C: + // Prevent the loop from spinning too fast + continue } } } - -func ipToAddress(ip net.IP) string { - ports := []int{500, 854, 859, 864, 878, 880, 890, 891, 894, 903, 908, 928, 934, 939, 942, - 943, 945, 946, 955, 968, 987, 988, 1002, 1010, 1014, 1018, 1070, 1074, 1180, 1387, 1701, - 1843, 2371, 2408, 2506, 3138, 3476, 3581, 3854, 4177, 4198, 4233, 4500, 5279, - 5956, 7103, 7152, 7156, 7281, 7559, 8319, 8742, 8854, 8886} - - // Pick a random port number - b := make([]byte, 8) - n, err := rand.Read(b) - if n != 8 { - panic(n) - } else if err != nil { - panic(err) - } - serverAddress := fmt.Sprintf("%s:%d", ip.String(), ports[int(b[0])%len(ports)]) - if strings.Contains(ip.String(), ":") { - //ip6 - serverAddress = fmt.Sprintf("[%s]:%d", ip.String(), ports[int(b[0])%len(ports)]) - } - return serverAddress -} diff --git a/wiresocks/udpfw.go b/wiresocks/udpfw.go index 820168775..20dd8193d 100644 --- a/wiresocks/udpfw.go +++ b/wiresocks/udpfw.go @@ -3,6 +3,7 @@ package wiresocks import ( "context" "encoding/binary" + "errors" "fmt" "io" "net" @@ -152,7 +153,7 @@ func socks5Handshake(conn net.Conn) error { } if resp[0] != 0x05 || resp[1] != 0x00 { - return fmt.Errorf("invalid SOCKS5 authentication response") + return errors.New("invalid SOCKS5 authentication response") } return nil } @@ -219,7 +220,7 @@ func requestUDPAssociate(conn net.Conn) (*net.UDPAddr, error) { } if resp[1] != 0x00 { - return nil, fmt.Errorf("UDP ASSOCIATE request failed") + return nil, errors.New("UDP ASSOCIATE request failed") } // Parse the proxy UDP address diff --git a/wiresocks/wiresocks.go b/wiresocks/wiresocks.go index 1f920a8f0..c8898d337 100644 --- a/wiresocks/wiresocks.go +++ b/wiresocks/wiresocks.go @@ -4,10 +4,8 @@ import ( "bytes" "context" "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" @@ -22,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)) - } - - 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 - `)) + 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)) + + 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 @@ -76,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