From ae47c6ea7657a23599e8908dd742d3c39ae10d30 Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Mon, 11 Mar 2024 16:16:35 +0000 Subject: [PATCH 1/7] ipscanner: add ipscanner and random refactors Signed-off-by: Mark Pashmfouroush --- app/app.go | 17 +- go.mod | 9 +- go.sum | 2 - ipscanner/LICENSE | 21 ++ ipscanner/README.md | 38 ++++ ipscanner/example/cfscanner/main.go | 13 ++ ipscanner/example/warpscanner/main.go | 63 +++++ ipscanner/internal/cache/cache.go | 60 +++++ ipscanner/internal/engine/engine.go | 104 +++++++++ ipscanner/internal/engine/queue.go | 168 ++++++++++++++ ipscanner/internal/iterator/iterator.go | 280 +++++++++++++++++++++++ ipscanner/internal/ping/http.go | 121 ++++++++++ ipscanner/internal/ping/ping.go | 114 ++++++++++ ipscanner/internal/ping/quic.go | 88 +++++++ ipscanner/internal/ping/tcp.go | 84 +++++++ ipscanner/internal/ping/tls.go | 83 +++++++ ipscanner/internal/ping/warp.go | 290 ++++++++++++++++++++++++ ipscanner/internal/statute/default.go | 219 ++++++++++++++++++ ipscanner/internal/statute/ping.go | 48 ++++ ipscanner/internal/statute/queue.go | 34 +++ ipscanner/internal/statute/statute.go | 65 ++++++ ipscanner/scanner.go | 283 +++++++++++++++++++++++ iputils/iputils.go | 94 ++++++++ psiphon/p.go | 4 +- warp/account.go | 16 +- warp/endpoint.go | 84 ++++++- warp/key.go | 1 + warp/tls.go | 86 ++----- wiresocks/config.go | 3 +- wiresocks/scanner.go | 41 +--- wiresocks/wiresocks.go | 1 - 31 files changed, 2390 insertions(+), 144 deletions(-) create mode 100644 ipscanner/LICENSE create mode 100644 ipscanner/README.md create mode 100644 ipscanner/example/cfscanner/main.go create mode 100644 ipscanner/example/warpscanner/main.go create mode 100644 ipscanner/internal/cache/cache.go create mode 100644 ipscanner/internal/engine/engine.go create mode 100644 ipscanner/internal/engine/queue.go create mode 100644 ipscanner/internal/iterator/iterator.go create mode 100644 ipscanner/internal/ping/http.go create mode 100644 ipscanner/internal/ping/ping.go create mode 100644 ipscanner/internal/ping/quic.go create mode 100644 ipscanner/internal/ping/tcp.go create mode 100644 ipscanner/internal/ping/tls.go create mode 100644 ipscanner/internal/ping/warp.go create mode 100644 ipscanner/internal/statute/default.go create mode 100644 ipscanner/internal/statute/ping.go create mode 100644 ipscanner/internal/statute/queue.go create mode 100644 ipscanner/internal/statute/statute.go create mode 100644 ipscanner/scanner.go create mode 100644 iputils/iputils.go diff --git a/app/app.go b/app/app.go index f62913f71..894b0a944 100644 --- a/app/app.go +++ b/app/app.go @@ -43,7 +43,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,20 +55,23 @@ 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 } + endpoints = make([]string, len(res)) + for i := 0; i < len(res); i++ { + endpoints[i] = res[i].String() + } } var warpErr error @@ -219,7 +222,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 +230,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/go.mod b/go.mod index 535bfc02b..c7c5d0725 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,13 @@ replace github.com/Psiphon-Labs/psiphon-tunnel-core => github.com/bepass-org/psi 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/bepass-org/proxy v0.0.0-20240201095508-c86216dd0aea + github.com/davecgh/go-spew v1.1.1 + github.com/flynn/noise v1.1.0 github.com/go-ini/ini v1.67.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 @@ -35,18 +38,15 @@ require ( 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/libp2p/go-reuseport v0.4.0 // indirect @@ -63,7 +63,6 @@ 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 diff --git a/go.sum b/go.sum index f49965b1a..9e95d468a 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,6 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/ 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= 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..cacaa8de8 --- /dev/null +++ b/ipscanner/example/cfscanner/main.go @@ -0,0 +1,13 @@ +package main + +import "github.com/bepass-org/wireguard-go/ipscanner" + +func main() { + // new scanner + scanner := ipscanner.NewScanner( + ipscanner.WithHTTPPing(), + ipscanner.WithUseIPv6(true), + ) + go scanner.Run() + select {} +} diff --git a/ipscanner/example/warpscanner/main.go b/ipscanner/example/warpscanner/main.go new file mode 100644 index 000000000..f6fb1264c --- /dev/null +++ b/ipscanner/example/warpscanner/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + "net" + "net/netip" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner" + "github.com/bepass-org/wireguard-go/warp" +) + +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 []netip.AddrPort) { + // new scanner + scanner := ipscanner.NewScanner( + ipscanner.WithWarpPing(), + ipscanner.WithWarpPrivateKey(privKey), + ipscanner.WithWarpPeerPublicKey(pubKey), + ipscanner.WithUseIPv6(canConnectIPv6(googlev6DNSAddr80)), + ipscanner.WithUseIPv4(true), + ipscanner.WithMaxDesirableRTT(500), + ipscanner.WithCidrList(warp.WarpPrefixes()), + ) + scanner.Run() + var ipList []netip.Addr + for { + ipList = scanner.GetAvailableIPs() + if len(ipList) > 2 { + scanner.Stop() + break + } + time.Sleep(1 * time.Second) + } + for i := 0; i < 2; i++ { + result = append(result, netip.AddrPortFrom(ipList[i], warp.RandomWarpPort())) + } + return +} + +func main() { + fmt.Println(RunScan(privKey, pubKey)) + time.Sleep(10 * time.Second) +} 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..cf16749d8 --- /dev/null +++ b/ipscanner/internal/engine/engine.go @@ -0,0 +1,104 @@ +package engine + +import ( + "context" + "fmt" + "net/netip" + "strings" + "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 + ctx context.Context + cancelFunc context.CancelFunc + ping func(netip.Addr) (int, error) + statute.Logger +} + +func NewScannerEngine(opts *statute.ScannerOptions, ctx ...context.Context) *Engine { + queue := NewIPQueue(opts) + var contextToUse context.Context + var cancel context.CancelFunc + + if len(ctx) > 0 { + contextToUse = ctx[0] + } else { + contextToUse, cancel = context.WithCancel(context.Background()) + } + p := ping.Ping{ + Options: opts, + } + return &Engine{ + ipQueue: queue, + ctx: contextToUse, + cancelFunc: cancel, + ping: p.DoPing, + generator: iterator.NewIterator(opts), + Logger: opts.Logger, + } +} + +func (e *Engine) GetAvailableIPs(desc bool) []netip.Addr { + if e.ipQueue != nil { + return e.ipQueue.AvailableIPs(desc) + } + return nil +} + +func (e *Engine) Run() { + for { + select { + case <-e.ctx.Done(): + fmt.Println("Context Done!") + return + case <-e.ipQueue.available: + e.Logger.Debug("New Scanning Round Started") + batch, err := e.generator.NextBatch() + if err != nil { + e.Logger.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 <-e.ctx.Done(): + fmt.Println("Context Done!") + return + default: + e.Logger.Debug("Pinging IP: %s", ip) + if rtt, err := e.ping(ip); err == nil { + ipInfo := statute.IPInfo{ + IP: ip, + RTT: rtt, + CreatedAt: time.Now(), + } + e.Logger.Debug("IP: %s, RTT: %d", ip, rtt) + e.ipQueue.Enqueue(ipInfo) + } else { + // if timeout error + if strings.Contains(err.Error(), ": i/o timeout") { + e.Logger.Debug("Timeout Error: %s", ip) + continue + } + e.Logger.Error("Error while pinging IP: %s, Error: %v", ip, err) + } + } + } + default: + e.Logger.Debug("Engine: call the expire function") + e.ipQueue.Expire() + time.Sleep(200 * time.Millisecond) + } + } +} + +func (e *Engine) Cancel() { + e.cancelFunc() +} diff --git a/ipscanner/internal/engine/queue.go b/ipscanner/internal/engine/queue.go new file mode 100644 index 000000000..87b4ba0a2 --- /dev/null +++ b/ipscanner/internal/engine/queue.go @@ -0,0 +1,168 @@ +package engine + +import ( + "net/netip" + "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 int + inIdealMode bool + onChangeCallback statute.TIPQueueChangeCallback + logger statute.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), + onChangeCallback: opts.IPQueueChangeCallback, + logger: opts.Logger, + reserved: reserved, + } +} + +func (q *IPQueue) Enqueue(info statute.IPInfo) bool { + q.mu.Lock() + defer q.mu.Unlock() + + defer func() { + q.onChangeCallback(q.queue) + }() + + q.logger.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.logger.Debug("Enqueue: empty queue adding first available item") + q.queue = append(q.queue, info) + return false + } + + if info.RTT <= q.rttThreshold { + q.logger.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.logger.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.logger.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.logger.Debug("Enqueue: The Queue is full but we keep the new item in the reserved queue.") + q.reserved.Enqueue(info) + } + } + + q.logger.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.logger.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.logger.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() { + go q.onChangeCallback(q.queue) + }() + 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.logger.Debug("Expire: Not in ideal mode") + q.available <- struct{}{} + return + } + + q.logger.Debug("Expire: In ideal mode") + defer func() { + q.onChangeCallback(q.queue) + }() + + 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.logger.Debug("Expire: Removing expired item from queue") + shouldStartNewScan = true + } else { + resQ = append(resQ, q.queue[i]) + } + } + q.queue = resQ + q.logger.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) []netip.Addr { + 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 + }) + + ips := make([]netip.Addr, len(sortedQueue)) + for i, info := range sortedQueue { + ips[i] = info.IP + } + + return ips +} diff --git a/ipscanner/internal/iterator/iterator.go b/ipscanner/internal/iterator/iterator.go new file mode 100644 index 000000000..20627e4cc --- /dev/null +++ b/ipscanner/internal/iterator/iterator.go @@ -0,0 +1,280 @@ +package iterator + +import ( + "crypto/rand" + "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, fmt.Errorf("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..90ac553c4 --- /dev/null +++ b/ipscanner/internal/ping/http.go @@ -0,0 +1,121 @@ +package ping + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "net/netip" + "net/url" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type HttpPingResult struct { + Time int + Proto string + Status int + Length int + Err error + IP netip.Addr +} + +func (h *HttpPingResult) Result() int { + return h.Time +} + +func (h *HttpPingResult) Error() error { + return h.Err +} + +func (h *HttpPingResult) String() string { + if h.Err != nil { + return fmt.Sprintf("%s", h.Err) + } else { + return fmt.Sprintf("%s: protocol=%s, status=%d, length=%d, time=%d ms", h.IP.String(), h.Proto, h.Status, h.Length, h.Time) + } +} + +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 + port := u.Port() + ip := statute.CloneIP(h.IP) + if !ip.IsValid() { + return h.errorResult(fmt.Errorf("no IP specified")) + } + ipstr := ip.String() + if statute.IsIPv6(ip) { + ipstr = fmt.Sprintf("[%s]", ipstr) + } + targetAddr := net.JoinHostPort(ipstr, port) + + 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 + + client := h.opts.HttpClientFunc(h.opts.RawDialerFunc, h.opts.TLSDialerFunc, h.opts.QuicDialerFunc, targetAddr) + + 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) + } + return &HttpPingResult{int(time.Since(t0).Milliseconds()), resp.Proto, resp.StatusCode, len(body), nil, ip} +} + +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..1ea02a5c5 --- /dev/null +++ b/ipscanner/internal/ping/ping.go @@ -0,0 +1,114 @@ +package ping + +import ( + "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) (int, error) { + var sum, ops, hp, tp int + var err error + if p.Options.SelectedOps&statute.HTTPPing > 0 { + hp, err = p.httpPing(ip) + if err != nil { + return 0, err + } + ops++ + sum += hp + } + if p.Options.SelectedOps&statute.TLSPing > 0 { + tp, err = p.tlsPing(ip) + if err != nil { + return 0, err + } + ops++ + sum += tp + } + if p.Options.SelectedOps&statute.TCPPing > 0 { + tp, err = p.tcpPing(ip) + if err != nil { + return 0, err + } + ops++ + sum += tp + } + if p.Options.SelectedOps&statute.QUICPing > 0 { + tp, err = p.quicPing(ip) + if err != nil { + return 0, err + } + ops++ + sum += tp + } + if p.Options.SelectedOps&statute.WARPPing > 0 { + tp, err = p.warpPing(ip) + if err != nil { + return 0, err + } + ops++ + sum += tp + } + if ops == 0 { + return 99, nil + } + return sum / ops, nil +} + +func (p *Ping) httpPing(ip netip.Addr) (int, 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) (int, error) { + return p.calc( + NewWarpPing( + ip, + p.Options, + ), + ) +} + +func (p *Ping) tlsPing(ip netip.Addr) (int, error) { + return p.calc( + NewTlsPing(ip, p.Options.Hostname, p.Options.Port, p.Options), + ) +} + +func (p *Ping) tcpPing(ip netip.Addr) (int, error) { + return p.calc( + NewTcpPing(ip, p.Options.Hostname, p.Options.Port, p.Options), + ) +} + +func (p *Ping) quicPing(ip netip.Addr) (int, error) { + return p.calc( + NewQuicPing(ip, p.Options.Hostname, p.Options.Port, p.Options), + ) +} + +func (p *Ping) calc(tp statute.IPing) (int, error) { + pr := tp.Ping() + err := pr.Error() + if err != nil { + return 0, 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..38674785a --- /dev/null +++ b/ipscanner/internal/ping/quic.go @@ -0,0 +1,88 @@ +package ping + +import ( + "context" + "fmt" + "net" + "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 { + Time int + Err error + IP netip.Addr + QUICVersion uint32 + TLSVersion uint16 +} + +func (h *QuicPingResult) Result() int { + return h.Time +} + +func (h *QuicPingResult) Error() error { + return h.Err +} + +func (h *QuicPingResult) String() string { + if h.Err != nil { + return fmt.Sprintf("%s", h.Err) + } else { + return fmt.Sprintf("%s: quic=%s, tls=%s, time=%d ms", h.IP.String(), quic.VersionNumber(h.QUICVersion).String(), statute.TlsVersionToString(h.TLSVersion), h.Time) + } +} + +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 { + ip := statute.CloneIP(h.IP) + if !ip.IsValid() { + return h.errorResult(fmt.Errorf("no IP specified")) + } + addr := net.JoinHostPort(ip.String(), fmt.Sprint(h.Port)) + + t0 := time.Now() + conn, err := h.opts.QuicDialerFunc(ctx, addr, nil, nil) + if err != nil { + return h.errorResult(err) + } + + defer conn.CloseWithError(quic.ApplicationErrorCode(uint64(http3.ErrCodeNoError)), "") + return &QuicPingResult{int(time.Since(t0).Milliseconds()), nil, ip, uint32(conn.ConnectionState().Version), conn.ConnectionState().TLS.Version} +} + +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..1215adbff --- /dev/null +++ b/ipscanner/internal/ping/tcp.go @@ -0,0 +1,84 @@ +package ping + +import ( + "context" + "fmt" + "net" + "net/netip" + "strconv" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type TcpPingResult struct { + Time int + Err error + IP netip.Addr +} + +func (tp *TcpPingResult) Result() int { + return tp.Time +} + +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.IP.String(), tp.Time) + } +} + +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 { + ip := statute.CloneIP(tp.ip) + if !ip.IsValid() { + return &TcpPingResult{0, fmt.Errorf("no IP specified"), netip.Addr{}} + } + t0 := time.Now() + conn, err := tp.opts.RawDialerFunc(ctx, "tcp", net.JoinHostPort(ip.String(), strconv.FormatUint(uint64(tp.Port), 10))) + if err != nil { + return &TcpPingResult{0, err, netip.Addr{}} + } + defer conn.Close() + return &TcpPingResult{int(time.Since(t0).Milliseconds()), nil, ip} +} + +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..3b1ab4602 --- /dev/null +++ b/ipscanner/internal/ping/tls.go @@ -0,0 +1,83 @@ +package ping + +import ( + "context" + "fmt" + "net" + "net/netip" + "strconv" + "time" + + "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" +) + +type TlsPingResult struct { + Time int + TLSVersion uint16 + Err error + IP netip.Addr +} + +func (t *TlsPingResult) Result() int { + return t.Time +} + +func (t *TlsPingResult) Error() error { + return t.Err +} + +func (t *TlsPingResult) String() string { + if t.Err != nil { + return fmt.Sprintf("%s", t.Err) + } else { + return fmt.Sprintf("%s: protocol=%s, time=%d ms", t.IP.String(), statute.TlsVersionToString(t.TLSVersion), t.Result()) + } +} + +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 { + ip := statute.CloneIP(t.IP) + + if !ip.IsValid() { + return t.errorResult(fmt.Errorf("no IP specified")) + } + + t0 := time.Now() + client, err := t.opts.TLSDialerFunc(ctx, "tcp", net.JoinHostPort(ip.String(), strconv.FormatUint(uint64(t.Port), 10))) + if err != nil { + return t.errorResult(err) + } + defer client.Close() + return &TlsPingResult{int(time.Since(t0).Milliseconds()), t.opts.TlsVersion, nil, ip} +} + +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..a0518e8fd --- /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/davecgh/go-spew/spew" + "github.com/flynn/noise" + "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/curve25519" +) + +type WarpPingResult struct { + Time int + Err error + IP netip.Addr +} + +func (h *WarpPingResult) Result() int { + return h.Time +} + +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.IP.String(), "warp", h.Time) + } +} + +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 { + t0 := time.Now() + + err := initiateHandshake( + netip.AddrPortFrom(h.IP, warp.RandomWarpPort()), + h.PrivateKey, + h.PeerPublicKey, + h.PresharedKey, + ) + if err != nil { + return h.errorResult(err) + } + return &WarpPingResult{int(time.Since(t0).Milliseconds()), nil, h.IP} +} + +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) error { + staticKeyPair, err := staticKeypair(privateKeyBase64) + if err != nil { + return err + } + + peerPublicKey, err := base64.StdEncoding.DecodeString(peerPublicKeyBase64) + if err != nil { + return err + } + + presharedKey, err := base64.StdEncoding.DecodeString(presharedKeyBase64) + if err != nil { + return err + } + + if presharedKeyBase64 == "" { + presharedKey = make([]byte, 32) + } + + ephemeral, err := ephemeralKeypair() + if err != nil { + return 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 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 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 err + } + _, err = hasher.Write(initiationPacket.Bytes()) + if err != nil { + return 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 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 fmt.Errorf("error generating random packet: %v", err) + } + + // Send the random packet + _, err = conn.Write(randomPacket) + if err != nil { + return fmt.Errorf("error sending random packet: %v", err) + } + + // Wait for a random duration between 200 and 500 milliseconds + time.Sleep(time.Duration(randomInt(200, 500)) * time.Millisecond) + } + + // spew.Dump(initiationPacket) + _, err = initiationPacket.WriteTo(conn) + if err != nil { + return err + } + + response := make([]byte, 92) + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + i, err := conn.Read(response) + fmt.Println("server response, len+packet: ", i, response[12:60]) + if err != nil { + return err + } + + // Check the response type + if response[0] != 2 { // 2 is the message type for response + return 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 errors.New("invalid sender index in response") + } + + payload, _, _, err := hs.ReadMessage(nil, response[12:60]) + spew.Dump(payload) + if err != nil { + spew.Dump(err) + return err + } + + // Check if the payload is empty (as expected in WireGuard handshake) + if len(payload) != 0 { + return errors.New("unexpected payload in response") + } + + fmt.Println("Handshake completed successfully") + return 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..2c71910aa --- /dev/null +++ b/ipscanner/internal/statute/default.go @@ -0,0 +1,219 @@ +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) +} + +// default logger + +type Logger interface { + Debug(s string, v ...interface{}) + Error(s string, v ...interface{}) +} + +type DefaultLogger struct{} + +func (l DefaultLogger) Debug(s string, v ...interface{}) { + fmt.Printf(fmt.Sprintf("%s\r\n", s), v...) +} + +func (l DefaultLogger) Error(s string, v ...interface{}) { + fmt.Printf(fmt.Sprintf("%s\r\n", s), v...) +} + +func DefaultIPQueueChangeCallback(ips []IPInfo) { + fmt.Printf("queue change: %d\r\n", len(ips)) + for _, ip := range ips { + fmt.Printf("IP:%s\tRTT:%d\tTS:%s\r\n", ip.IP.String(), ip.RTT, ip.CreatedAt.String()) + } +} + +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..42702141d --- /dev/null +++ b/ipscanner/internal/statute/ping.go @@ -0,0 +1,48 @@ +package statute + +import ( + "context" + "crypto/tls" + "fmt" + "net/netip" +) + +type IPingResult interface { + Result() int + 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" + } +} + +func IsIPv4(ip netip.Addr) bool { + return ip.Is4() +} + +func IsIPv6(ip netip.Addr) bool { + return ip.Is6() +} + +func CloneIP(ip netip.Addr) netip.Addr { + return ip +} 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..19fe6bea7 --- /dev/null +++ b/ipscanner/internal/statute/statute.go @@ -0,0 +1,65 @@ +package statute + +import ( + "context" + "crypto/tls" + "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 { + IP netip.Addr + RTT int + CreatedAt time.Time +} + +type ScannerOptions struct { + UseIPv4 bool + UseIPv6 bool + CidrList []netip.Prefix // CIDR ranges to scan + SelectedOps int + Logger 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 int + 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..3da7343c3 --- /dev/null +++ b/ipscanner/scanner.go @@ -0,0 +1,283 @@ +package ipscanner + +import ( + "crypto/tls" + "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 + logger statute.Logger + engine *engine.Engine + // onChange func([]netip.Addr) +} + +func NewScanner(options ...Option) *IPScanner { + p := &IPScanner{ + options: statute.ScannerOptions{ + UseIPv4: true, + UseIPv6: true, + CidrList: statute.DefaultCFRanges(), + SelectedOps: 0, + Logger: statute.DefaultLogger{}, + 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, + IPQueueTTL: 30 * time.Second, + IPQueueChangeCallback: statute.DefaultIPQueueChangeCallback, + ConnectionTimeout: 1 * time.Second, + HandshakeTimeout: 1 * time.Second, + TlsVersion: tls.VersionTLS13, + }, + logger: statute.DefaultLogger{}, + } + + 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 statute.Logger) Option { + return func(i *IPScanner) { + 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 int) Option { + return func(i *IPScanner) { + i.options.MaxDesirableRTT = threshold + } +} + +func WithIPQueueTTL(ttl time.Duration) Option { + return func(i *IPScanner) { + i.options.IPQueueTTL = ttl + } +} + +func WithIPQueueChangeCallback(callback statute.TIPQueueChangeCallback) Option { + return func(i *IPScanner) { + i.options.IPQueueChangeCallback = callback + } +} + +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 + } +} + +func (i *IPScanner) SetIPQueueChangeCallback(callback statute.TIPQueueChangeCallback) { + i.options.IPQueueChangeCallback = callback +} + +// 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() { + statute.FinalOptions = &i.options + if !i.options.UseIPv4 && !i.options.UseIPv6 { + i.logger.Error("Fatal: both IPv4 and IPv6 are disabled, nothing to do") + return + } + i.engine = engine.NewScannerEngine(&i.options) + go i.engine.Run() +} + +func (i *IPScanner) Stop() { + i.engine.Cancel() +} + +func (i *IPScanner) GetAvailableIPs() []netip.Addr { + 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..d82e02e81 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") diff --git a/warp/account.go b/warp/account.go index e02b55872..a675a1374 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 { @@ -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..2c1a3af0f 100644 --- a/warp/key.go +++ b/warp/key.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/base64" "fmt" + "golang.org/x/crypto/curve25519" ) diff --git a/warp/tls.go b/warp/tls.go index 324620321..0de1b204c 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,13 +153,11 @@ 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) } err = utlsConn.Handshake() - if err != nil { return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err) } @@ -167,59 +165,13 @@ func (d *Dialer) makeTLSHelloPacketWithSNICurve(plainConn net.Conn, config *tls. 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..905da8088 100644 --- a/wiresocks/config.go +++ b/wiresocks/config.go @@ -4,11 +4,10 @@ import ( "encoding/base64" "encoding/hex" "errors" + "net/netip" "strings" "github.com/go-ini/ini" - - "net/netip" ) type PeerConfig struct { diff --git a/wiresocks/scanner.go b/wiresocks/scanner.go index 61e41141b..cbe58fba8 100644 --- a/wiresocks/scanner.go +++ b/wiresocks/scanner.go @@ -2,14 +2,12 @@ package wiresocks import ( "context" - "crypto/rand" "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 +25,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 []netip.AddrPort, 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 +37,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(), @@ -54,7 +45,7 @@ func RunScan(ctx context.Context, rtt time.Duration) (result []string, err error ipscanner.WithUseIPv6(canConnectIPv6(netip.MustParseAddrPort("[2001:4860:4860::8888]:80"))), ipscanner.WithUseIPv4(true), ipscanner.WithMaxDesirableRTT(int(rtt.Milliseconds())), - ipscanner.WithCidrList(stringedPrefixes), + ipscanner.WithCidrList(warp.WarpPrefixes()), ) scanner.Run() @@ -72,11 +63,11 @@ func RunScan(ctx context.Context, rtt time.Duration) (result []string, err error scanner.Stop() return nil, fmt.Errorf("scanner maximum time exceeded") default: - ipList := scanner.GetAvailableIPS() + ipList := scanner.GetAvailableIPs() if len(ipList) > 1 { scanner.Stop() for i := 0; i < 2; i++ { - result = append(result, ipToAddress(ipList[i])) + result = append(result, netip.AddrPortFrom(ipList[i], warp.RandomWarpPort())) } return result, nil } @@ -84,25 +75,3 @@ func RunScan(ctx context.Context, rtt time.Duration) (result []string, err error } } } - -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/wiresocks.go b/wiresocks/wiresocks.go index 1f920a8f0..2acddee6e 100644 --- a/wiresocks/wiresocks.go +++ b/wiresocks/wiresocks.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "net/netip" "github.com/MakeNowJust/heredoc/v2" From 7cc2721e9dd89312b83f5523ea98002919ef185d Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Mon, 11 Mar 2024 16:29:10 +0000 Subject: [PATCH 2/7] misc: better error wrapping Signed-off-by: Mark Pashmfouroush --- app/app.go | 6 +++--- ipscanner/internal/ping/warp.go | 4 ++-- psiphon/relay.go | 8 ++++---- warp/account.go | 2 +- warp/key.go | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/app.go b/app/app.go index 894b0a944..41c1a4e68 100644 --- a/app/app.go +++ b/app/app.go @@ -131,7 +131,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) @@ -200,7 +200,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 @@ -209,7 +209,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 diff --git a/ipscanner/internal/ping/warp.go b/ipscanner/internal/ping/warp.go index a0518e8fd..e290d9abb 100644 --- a/ipscanner/internal/ping/warp.go +++ b/ipscanner/internal/ping/warp.go @@ -216,13 +216,13 @@ func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKe randomPacket := make([]byte, packetSize) _, err := rand.Read(randomPacket) if err != nil { - return fmt.Errorf("error generating random packet: %v", err) + return fmt.Errorf("error generating random packet: %w", err) } // Send the random packet _, err = conn.Write(randomPacket) if err != nil { - return fmt.Errorf("error sending random packet: %v", err) + return fmt.Errorf("error sending random packet: %w", err) } // Wait for a random duration between 200 and 500 milliseconds 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 a675a1374..7a9a26389 100644 --- a/warp/account.go +++ b/warp/account.go @@ -578,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:") diff --git a/warp/key.go b/warp/key.go index 2c1a3af0f..e8bf0a688 100644 --- a/warp/key.go +++ b/warp/key.go @@ -24,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) From 0564b6c3b2569896999e3fdc40582562838d9789 Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Mon, 11 Mar 2024 17:46:54 +0000 Subject: [PATCH 3/7] go: use go1.21 Signed-off-by: Mark Pashmfouroush --- go.mod | 18 ++++++++--------- go.sum | 61 +++++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index c7c5d0725..a39cfd83c 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ 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/Psiphon-Labs/psiphon-tunnel-core v2.0.28+incompatible github.com/bepass-org/proxy v0.0.0-20240201095508-c86216dd0aea github.com/davecgh/go-spew v1.1.1 github.com/flynn/noise v1.1.0 @@ -16,9 +16,9 @@ require ( 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 + 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 ) @@ -29,10 +29,8 @@ 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 diff --git a/go.sum b/go.sum index 9e95d468a..bf67c3222 100644 --- a/go.sum +++ b/go.sum @@ -5,32 +5,31 @@ 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/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk= +github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4= github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag= 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/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= @@ -44,14 +43,19 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/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/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= @@ -59,12 +63,15 @@ github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67d 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= @@ -72,7 +79,9 @@ 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/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= @@ -81,6 +90,7 @@ 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= @@ -94,8 +104,11 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/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= @@ -103,13 +116,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= @@ -146,6 +165,7 @@ github.com/refraction-networking/obfs4 v0.1.2/go.mod h1:wAl/+gWiLsrcykJA3nKJHx89 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/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= @@ -174,8 +194,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= @@ -191,13 +211,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= @@ -211,15 +232,16 @@ golang.org/x/sys v0.5.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= @@ -241,6 +263,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= @@ -250,9 +273,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= From 8f534e243867f0df1357da9a3f8f0993c0bd7370 Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Tue, 12 Mar 2024 15:41:26 +0000 Subject: [PATCH 4/7] ipscanner: overhaul logging and ping code Signed-off-by: Mark Pashmfouroush --- app/app.go | 6 +- go.mod | 5 +- go.sum | 19 ++++- ipscanner/example/cfscanner/main.go | 14 +++- ipscanner/example/warpscanner/main.go | 62 ++++++++++++---- ipscanner/internal/engine/engine.go | 70 ++++++------------ ipscanner/internal/engine/queue.go | 101 +++++++++++++++----------- ipscanner/internal/ping/http.go | 48 ++++++------ ipscanner/internal/ping/ping.go | 72 ++++++++---------- ipscanner/internal/ping/quic.go | 36 +++++---- ipscanner/internal/ping/tcp.go | 34 ++++----- ipscanner/internal/ping/tls.go | 24 +++--- ipscanner/internal/ping/warp.go | 72 +++++++++--------- ipscanner/internal/statute/default.go | 24 ------ ipscanner/internal/statute/ping.go | 15 +--- ipscanner/internal/statute/statute.go | 9 ++- ipscanner/scanner.go | 89 ++++++++++------------- wiresocks/scanner.go | 40 +++++----- 18 files changed, 370 insertions(+), 370 deletions(-) diff --git a/app/app.go b/app/app.go index 41c1a4e68..6e001d087 100644 --- a/app/app.go +++ b/app/app.go @@ -68,11 +68,15 @@ func RunWarp(ctx context.Context, opts WarpOptions) error { 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].String() + endpoints[i] = res[i].AddrPort.String() } } + log.Printf("using warp endpoints: %+v", endpoints) var warpErr error switch { diff --git a/go.mod b/go.mod index a39cfd83c..ab91e1b1a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/Psiphon-Labs/psiphon-tunnel-core v2.0.28+incompatible github.com/bepass-org/proxy v0.0.0-20240201095508-c86216dd0aea - github.com/davecgh/go-spew v1.1.1 + github.com/fatih/color v1.16.0 github.com/flynn/noise v1.1.0 github.com/go-ini/ini v1.67.0 github.com/hashicorp/golang-lru v1.0.2 @@ -16,6 +16,7 @@ require ( 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 + 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 @@ -48,6 +49,8 @@ require ( github.com/juju/ratelimit v1.0.2 // indirect github.com/klauspost/compress v1.16.7 // 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 diff --git a/go.sum b/go.sum index bf67c3222..5769cd374 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d h1:rtM8HsT3NG37YPj 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= @@ -78,8 +80,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 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= @@ -105,6 +107,13 @@ github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQsc 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= @@ -164,6 +173,10 @@ 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/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= @@ -228,7 +241,9 @@ 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= diff --git a/ipscanner/example/cfscanner/main.go b/ipscanner/example/cfscanner/main.go index cacaa8de8..a87796135 100644 --- a/ipscanner/example/cfscanner/main.go +++ b/ipscanner/example/cfscanner/main.go @@ -1,6 +1,10 @@ package main -import "github.com/bepass-org/wireguard-go/ipscanner" +import ( + "context" + + "github.com/bepass-org/wireguard-go/ipscanner" +) func main() { // new scanner @@ -8,6 +12,10 @@ func main() { ipscanner.WithHTTPPing(), ipscanner.WithUseIPv6(true), ) - go scanner.Run() - select {} + 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 index f6fb1264c..cb3d266f5 100644 --- a/ipscanner/example/warpscanner/main.go +++ b/ipscanner/example/warpscanner/main.go @@ -1,13 +1,18 @@ package main import ( - "fmt" + "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 ( @@ -30,34 +35,59 @@ func canConnectIPv6(remoteAddr netip.AddrPort) bool { return true } -func RunScan(privKey, pubKey string) (result []netip.AddrPort) { +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), + ipscanner.WithMaxDesirableRTT(500*time.Millisecond), ipscanner.WithCidrList(warp.WarpPrefixes()), ) - scanner.Run() - var ipList []netip.Addr + + 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) > 2 { - scanner.Stop() - break + 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 } - time.Sleep(1 * time.Second) - } - for i := 0; i < 2; i++ { - result = append(result, netip.AddrPortFrom(ipList[i], warp.RandomWarpPort())) } - return } func main() { - fmt.Println(RunScan(privKey, pubKey)) - time.Sleep(10 * time.Second) + 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/engine/engine.go b/ipscanner/internal/engine/engine.go index cf16749d8..e6f9aea66 100644 --- a/ipscanner/internal/engine/engine.go +++ b/ipscanner/internal/engine/engine.go @@ -2,9 +2,8 @@ package engine import ( "context" - "fmt" + "log/slog" "net/netip" - "strings" "time" "github.com/bepass-org/wireguard-go/ipscanner/internal/iterator" @@ -13,92 +12,65 @@ import ( ) type Engine struct { - generator *iterator.IpGenerator - ipQueue *IPQueue - ctx context.Context - cancelFunc context.CancelFunc - ping func(netip.Addr) (int, error) - statute.Logger + generator *iterator.IpGenerator + ipQueue *IPQueue + ping func(netip.Addr) (statute.IPInfo, error) + log *slog.Logger } -func NewScannerEngine(opts *statute.ScannerOptions, ctx ...context.Context) *Engine { +func NewScannerEngine(opts *statute.ScannerOptions) *Engine { queue := NewIPQueue(opts) - var contextToUse context.Context - var cancel context.CancelFunc - if len(ctx) > 0 { - contextToUse = ctx[0] - } else { - contextToUse, cancel = context.WithCancel(context.Background()) - } p := ping.Ping{ Options: opts, } return &Engine{ - ipQueue: queue, - ctx: contextToUse, - cancelFunc: cancel, - ping: p.DoPing, - generator: iterator.NewIterator(opts), - Logger: opts.Logger, + ipQueue: queue, + ping: p.DoPing, + generator: iterator.NewIterator(opts), + log: opts.Logger.With(slog.String("subsystem", "engine")), } } -func (e *Engine) GetAvailableIPs(desc bool) []netip.Addr { +func (e *Engine) GetAvailableIPs(desc bool) []statute.IPInfo { if e.ipQueue != nil { return e.ipQueue.AvailableIPs(desc) } return nil } -func (e *Engine) Run() { +func (e *Engine) Run(ctx context.Context) { for { select { - case <-e.ctx.Done(): - fmt.Println("Context Done!") + case <-ctx.Done(): return case <-e.ipQueue.available: - e.Logger.Debug("New Scanning Round Started") + e.log.Debug("Started new scanning round") batch, err := e.generator.NextBatch() if err != nil { - e.Logger.Error("Error while generating IP: %v", err) + 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 <-e.ctx.Done(): - fmt.Println("Context Done!") + case <-ctx.Done(): return default: - e.Logger.Debug("Pinging IP: %s", ip) - if rtt, err := e.ping(ip); err == nil { - ipInfo := statute.IPInfo{ - IP: ip, - RTT: rtt, - CreatedAt: time.Now(), - } - e.Logger.Debug("IP: %s, RTT: %d", ip, rtt) + 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 { - // if timeout error - if strings.Contains(err.Error(), ": i/o timeout") { - e.Logger.Debug("Timeout Error: %s", ip) - continue - } - e.Logger.Error("Error while pinging IP: %s, Error: %v", ip, err) + e.log.Error("ping error", "addr", ip, "error", err) } } } default: - e.Logger.Debug("Engine: call the expire function") + e.log.Debug("calling expire") e.ipQueue.Expire() time.Sleep(200 * time.Millisecond) } } } - -func (e *Engine) Cancel() { - e.cancelFunc() -} diff --git a/ipscanner/internal/engine/queue.go b/ipscanner/internal/engine/queue.go index 87b4ba0a2..6436fec91 100644 --- a/ipscanner/internal/engine/queue.go +++ b/ipscanner/internal/engine/queue.go @@ -1,7 +1,7 @@ package engine import ( - "net/netip" + "log/slog" "sort" "sync" "time" @@ -10,29 +10,27 @@ import ( ) type IPQueue struct { - queue []statute.IPInfo - maxQueueSize int - mu sync.Mutex - available chan struct{} - maxTTL time.Duration - rttThreshold int - inIdealMode bool - onChangeCallback statute.TIPQueueChangeCallback - logger statute.Logger - reserved statute.IPInfQueue + 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), - onChangeCallback: opts.IPQueueChangeCallback, - logger: opts.Logger, - reserved: reserved, + 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, } } @@ -41,43 +39,51 @@ func (q *IPQueue) Enqueue(info statute.IPInfo) bool { defer q.mu.Unlock() defer func() { - q.onChangeCallback(q.queue) + 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.logger.Debug("Enqueue: Sorting queue by 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.logger.Debug("Enqueue: empty queue adding first available item") + q.log.Debug("Enqueue: empty queue adding first available item") q.queue = append(q.queue, info) return false } if info.RTT <= q.rttThreshold { - q.logger.Debug("Enqueue: the new item's RTT is less than at least one of the members.") + 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.logger.Debug("Enqueue: the queue is full, remove the item with the highest 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.logger.Debug("Enqueue: Insert the new item in a sorted position.") + 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.logger.Debug("Enqueue: The Queue is full but we keep the new item in the reserved queue.") + q.log.Debug("Enqueue: The Queue is full but we keep the new item in the reserved queue.") q.reserved.Enqueue(info) } } - q.logger.Debug("Enqueue: Checking if any member has a higher RTT than the threshold.") + 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.logger.Debug("Enqueue: All members have an RTT lower than the threshold.") + 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 @@ -85,13 +91,21 @@ func (q *IPQueue) Enqueue(info statute.IPInfo) bool { q.inIdealMode = true // ok wait for expiration signal - q.logger.Debug("Enqueue: All members have an RTT lower than the threshold. Waiting 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() { - go q.onChangeCallback(q.queue) + 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() @@ -113,28 +127,36 @@ func (q *IPQueue) Expire() { defer q.mu.Unlock() if !q.inIdealMode { - q.logger.Debug("Expire: Not in ideal mode") + q.log.Debug("Expire: Not in ideal mode") q.available <- struct{}{} return } - q.logger.Debug("Expire: In ideal mode") + q.log.Debug("Expire: In ideal mode") defer func() { - q.onChangeCallback(q.queue) + 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.logger.Debug("Expire: Removing expired item from queue") + q.log.Debug("Expire: Removing expired item from queue") shouldStartNewScan = true } else { resQ = append(resQ, q.queue[i]) } } q.queue = resQ - q.logger.Debug("Expire: Adding reserved items to queue") + 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()) } @@ -143,7 +165,7 @@ func (q *IPQueue) Expire() { } } -func (q *IPQueue) AvailableIPs(desc bool) []netip.Addr { +func (q *IPQueue) AvailableIPs(desc bool) []statute.IPInfo { q.mu.Lock() defer q.mu.Unlock() @@ -159,10 +181,5 @@ func (q *IPQueue) AvailableIPs(desc bool) []netip.Addr { return sortedQueue[i].RTT < sortedQueue[j].RTT }) - ips := make([]netip.Addr, len(sortedQueue)) - for i, info := range sortedQueue { - ips[i] = info.IP - } - - return ips + return sortedQueue } diff --git a/ipscanner/internal/ping/http.go b/ipscanner/internal/ping/http.go index 90ac553c4..295d8c467 100644 --- a/ipscanner/internal/ping/http.go +++ b/ipscanner/internal/ping/http.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "net" "net/http" "net/netip" "net/url" @@ -14,16 +13,16 @@ import ( ) type HttpPingResult struct { - Time int - Proto string - Status int - Length int - Err error - IP netip.Addr + AddrPort netip.AddrPort + Proto string + Status int + Length int + RTT time.Duration + Err error } -func (h *HttpPingResult) Result() int { - return h.Time +func (h *HttpPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: h.AddrPort, RTT: h.RTT, CreatedAt: time.Now()} } func (h *HttpPingResult) Error() error { @@ -33,9 +32,9 @@ func (h *HttpPingResult) Error() error { func (h *HttpPingResult) String() string { if h.Err != nil { return fmt.Sprintf("%s", h.Err) - } else { - return fmt.Sprintf("%s: protocol=%s, status=%d, length=%d, time=%d ms", h.IP.String(), h.Proto, h.Status, h.Length, h.Time) } + + 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 { @@ -56,16 +55,10 @@ func (h *HttpPing) PingContext(ctx context.Context) statute.IPingResult { return h.errorResult(err) } orighost := u.Host - port := u.Port() - ip := statute.CloneIP(h.IP) - if !ip.IsValid() { + + if !h.IP.IsValid() { return h.errorResult(fmt.Errorf("no IP specified")) } - ipstr := ip.String() - if statute.IsIPv6(ip) { - ipstr = fmt.Sprintf("[%s]", ipstr) - } - targetAddr := net.JoinHostPort(ipstr, port) req, err := http.NewRequestWithContext(ctx, h.Method, h.URL, nil) if err != nil { @@ -81,22 +74,35 @@ func (h *HttpPing) PingContext(ctx context.Context) statute.IPingResult { } req.Host = orighost - client := h.opts.HttpClientFunc(h.opts.RawDialerFunc, h.opts.TLSDialerFunc, h.opts.QuicDialerFunc, targetAddr) + 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) } - return &HttpPingResult{int(time.Since(t0).Milliseconds()), resp.Proto, resp.StatusCode, len(body), nil, ip} + + 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 { diff --git a/ipscanner/internal/ping/ping.go b/ipscanner/internal/ping/ping.go index 1ea02a5c5..794c2e732 100644 --- a/ipscanner/internal/ping/ping.go +++ b/ipscanner/internal/ping/ping.go @@ -1,6 +1,7 @@ package ping import ( + "errors" "fmt" "net/netip" @@ -12,56 +13,52 @@ type Ping struct { } // DoPing performs a ping on the given IP address. -func (p *Ping) DoPing(ip netip.Addr) (int, error) { - var sum, ops, hp, tp int - var err error +func (p *Ping) DoPing(ip netip.Addr) (statute.IPInfo, error) { if p.Options.SelectedOps&statute.HTTPPing > 0 { - hp, err = p.httpPing(ip) + res, err := p.httpPing(ip) if err != nil { - return 0, err + return statute.IPInfo{}, err } - ops++ - sum += hp + + return res, nil } if p.Options.SelectedOps&statute.TLSPing > 0 { - tp, err = p.tlsPing(ip) + res, err := p.tlsPing(ip) if err != nil { - return 0, err + return statute.IPInfo{}, err } - ops++ - sum += tp + + return res, nil } if p.Options.SelectedOps&statute.TCPPing > 0 { - tp, err = p.tcpPing(ip) + res, err := p.tcpPing(ip) if err != nil { - return 0, err + return statute.IPInfo{}, err } - ops++ - sum += tp + + return res, nil } if p.Options.SelectedOps&statute.QUICPing > 0 { - tp, err = p.quicPing(ip) + res, err := p.quicPing(ip) if err != nil { - return 0, err + return statute.IPInfo{}, err } - ops++ - sum += tp + + return res, nil } if p.Options.SelectedOps&statute.WARPPing > 0 { - tp, err = p.warpPing(ip) + res, err := p.warpPing(ip) if err != nil { - return 0, err + return statute.IPInfo{}, err } - ops++ - sum += tp - } - if ops == 0 { - return 99, nil + + return res, nil } - return sum / ops, nil + + return statute.IPInfo{}, errors.New("no ping operation selected") } -func (p *Ping) httpPing(ip netip.Addr) (int, error) { +func (p *Ping) httpPing(ip netip.Addr) (statute.IPInfo, error) { return p.calc( NewHttpPing( ip, @@ -77,38 +74,33 @@ func (p *Ping) httpPing(ip netip.Addr) (int, error) { ) } -func (p *Ping) warpPing(ip netip.Addr) (int, error) { - return p.calc( - NewWarpPing( - ip, - 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) (int, error) { +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) (int, error) { +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) (int, error) { +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) (int, error) { +func (p *Ping) calc(tp statute.IPing) (statute.IPInfo, error) { pr := tp.Ping() err := pr.Error() if err != nil { - return 0, err + return statute.IPInfo{}, err } return pr.Result(), nil } diff --git a/ipscanner/internal/ping/quic.go b/ipscanner/internal/ping/quic.go index 38674785a..a01439c66 100644 --- a/ipscanner/internal/ping/quic.go +++ b/ipscanner/internal/ping/quic.go @@ -3,7 +3,6 @@ package ping import ( "context" "fmt" - "net" "net/netip" "time" @@ -14,15 +13,15 @@ import ( ) type QuicPingResult struct { - Time int - Err error - IP netip.Addr - QUICVersion uint32 + AddrPort netip.AddrPort + QUICVersion quic.VersionNumber TLSVersion uint16 + RTT time.Duration + Err error } -func (h *QuicPingResult) Result() int { - return h.Time +func (h *QuicPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: h.AddrPort, RTT: h.RTT, CreatedAt: time.Now()} } func (h *QuicPingResult) Error() error { @@ -32,9 +31,9 @@ func (h *QuicPingResult) Error() error { func (h *QuicPingResult) String() string { if h.Err != nil { return fmt.Sprintf("%s", h.Err) - } else { - return fmt.Sprintf("%s: quic=%s, tls=%s, time=%d ms", h.IP.String(), quic.VersionNumber(h.QUICVersion).String(), statute.TlsVersionToString(h.TLSVersion), h.Time) } + + 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 { @@ -50,20 +49,28 @@ func (h *QuicPing) Ping() statute.IPingResult { } func (h *QuicPing) PingContext(ctx context.Context) statute.IPingResult { - ip := statute.CloneIP(h.IP) - if !ip.IsValid() { + if !h.IP.IsValid() { return h.errorResult(fmt.Errorf("no IP specified")) } - addr := net.JoinHostPort(ip.String(), fmt.Sprint(h.Port)) + + addr := netip.AddrPortFrom(h.IP, h.Port) t0 := time.Now() - conn, err := h.opts.QuicDialerFunc(ctx, addr, nil, nil) + 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 &QuicPingResult{int(time.Since(t0).Milliseconds()), nil, ip, uint32(conn.ConnectionState().Version), conn.ConnectionState().TLS.Version} + return &res } func NewQuicPing(ip netip.Addr, host string, port uint16, opts *statute.ScannerOptions) *QuicPing { @@ -71,7 +78,6 @@ func NewQuicPing(ip netip.Addr, host string, port uint16, opts *statute.ScannerO IP: ip, Host: host, Port: port, - opts: *opts, } } diff --git a/ipscanner/internal/ping/tcp.go b/ipscanner/internal/ping/tcp.go index 1215adbff..2ef29150d 100644 --- a/ipscanner/internal/ping/tcp.go +++ b/ipscanner/internal/ping/tcp.go @@ -2,23 +2,22 @@ package ping import ( "context" + "errors" "fmt" - "net" "net/netip" - "strconv" "time" "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" ) type TcpPingResult struct { - Time int - Err error - IP netip.Addr + AddrPort netip.AddrPort + RTT time.Duration + Err error } -func (tp *TcpPingResult) Result() int { - return tp.Time +func (tp *TcpPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: tp.AddrPort, RTT: tp.RTT, CreatedAt: time.Now()} } func (tp *TcpPingResult) Error() error { @@ -29,13 +28,13 @@ func (tp *TcpPingResult) String() string { if tp.Err != nil { return fmt.Sprintf("%s", tp.Err) } else { - return fmt.Sprintf("%s: time=%d ms", tp.IP.String(), tp.Time) + return fmt.Sprintf("%s: time=%d ms", tp.AddrPort, tp.RTT) } } type TcpPing struct { host string - Port uint16 + port uint16 ip netip.Addr opts statute.ScannerOptions @@ -55,25 +54,26 @@ func (tp *TcpPing) Ping() statute.IPingResult { } func (tp *TcpPing) PingContext(ctx context.Context) statute.IPingResult { - ip := statute.CloneIP(tp.ip) - if !ip.IsValid() { - return &TcpPingResult{0, fmt.Errorf("no IP specified"), netip.Addr{}} + 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", net.JoinHostPort(ip.String(), strconv.FormatUint(uint64(tp.Port), 10))) + conn, err := tp.opts.RawDialerFunc(ctx, "tcp", addr.String()) if err != nil { - return &TcpPingResult{0, err, netip.Addr{}} + return &TcpPingResult{AddrPort: addr, RTT: 0, Err: err} } defer conn.Close() - return &TcpPingResult{int(time.Since(t0).Milliseconds()), nil, ip} + + 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, + port: port, ip: ip, - opts: *opts, } } diff --git a/ipscanner/internal/ping/tls.go b/ipscanner/internal/ping/tls.go index 3b1ab4602..b0aad1f4b 100644 --- a/ipscanner/internal/ping/tls.go +++ b/ipscanner/internal/ping/tls.go @@ -3,23 +3,21 @@ package ping import ( "context" "fmt" - "net" "net/netip" - "strconv" "time" "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" ) type TlsPingResult struct { - Time int + AddrPort netip.AddrPort TLSVersion uint16 + RTT time.Duration Err error - IP netip.Addr } -func (t *TlsPingResult) Result() int { - return t.Time +func (t *TlsPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: t.AddrPort, RTT: t.RTT, CreatedAt: time.Now()} } func (t *TlsPingResult) Error() error { @@ -29,9 +27,9 @@ func (t *TlsPingResult) Error() error { func (t *TlsPingResult) String() string { if t.Err != nil { return fmt.Sprintf("%s", t.Err) - } else { - return fmt.Sprintf("%s: protocol=%s, time=%d ms", t.IP.String(), statute.TlsVersionToString(t.TLSVersion), t.Result()) } + + return fmt.Sprintf("%s: protocol=%s, time=%d ms", t.AddrPort, statute.TlsVersionToString(t.TLSVersion), t.RTT) } type TlsPing struct { @@ -47,19 +45,17 @@ func (t *TlsPing) Ping() statute.IPingResult { } func (t *TlsPing) PingContext(ctx context.Context) statute.IPingResult { - ip := statute.CloneIP(t.IP) - - if !ip.IsValid() { + if !t.IP.IsValid() { return t.errorResult(fmt.Errorf("no IP specified")) } - + addr := netip.AddrPortFrom(t.IP, t.Port) t0 := time.Now() - client, err := t.opts.TLSDialerFunc(ctx, "tcp", net.JoinHostPort(ip.String(), strconv.FormatUint(uint64(t.Port), 10))) + client, err := t.opts.TLSDialerFunc(ctx, "tcp", addr.String()) if err != nil { return t.errorResult(err) } defer client.Close() - return &TlsPingResult{int(time.Since(t0).Milliseconds()), t.opts.TlsVersion, nil, ip} + 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 { diff --git a/ipscanner/internal/ping/warp.go b/ipscanner/internal/ping/warp.go index e290d9abb..fb165c156 100644 --- a/ipscanner/internal/ping/warp.go +++ b/ipscanner/internal/ping/warp.go @@ -15,20 +15,19 @@ import ( "github.com/bepass-org/wireguard-go/ipscanner/internal/statute" "github.com/bepass-org/wireguard-go/warp" - "github.com/davecgh/go-spew/spew" "github.com/flynn/noise" "golang.org/x/crypto/blake2s" "golang.org/x/crypto/curve25519" ) type WarpPingResult struct { - Time int - Err error - IP netip.Addr + AddrPort netip.AddrPort + RTT time.Duration + Err error } -func (h *WarpPingResult) Result() int { - return h.Time +func (h *WarpPingResult) Result() statute.IPInfo { + return statute.IPInfo{AddrPort: h.AddrPort, RTT: h.RTT, CreatedAt: time.Now()} } func (h *WarpPingResult) Error() error { @@ -39,7 +38,7 @@ 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.IP.String(), "warp", h.Time) + return fmt.Sprintf("%s: protocol=%s, time=%d ms", h.AddrPort, "warp", h.RTT) } } @@ -57,10 +56,9 @@ func (h *WarpPing) Ping() statute.IPingResult { } func (h *WarpPing) PingContext(_ context.Context) statute.IPingResult { - t0 := time.Now() - - err := initiateHandshake( - netip.AddrPortFrom(h.IP, warp.RandomWarpPort()), + addr := netip.AddrPortFrom(h.IP, warp.RandomWarpPort()) + rtt, err := initiateHandshake( + addr, h.PrivateKey, h.PeerPublicKey, h.PresharedKey, @@ -68,7 +66,8 @@ func (h *WarpPing) PingContext(_ context.Context) statute.IPingResult { if err != nil { return h.errorResult(err) } - return &WarpPingResult{int(time.Since(t0).Milliseconds()), nil, h.IP} + + return &WarpPingResult{AddrPort: addr, RTT: rtt, Err: nil} } func (h *WarpPing) errorResult(err error) *WarpPingResult { @@ -126,20 +125,20 @@ func randomInt(min, max int) int { return int(nBig.Int64()) + min } -func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKeyBase64, presharedKeyBase64 string) error { +func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKeyBase64, presharedKeyBase64 string) (time.Duration, error) { staticKeyPair, err := staticKeypair(privateKeyBase64) if err != nil { - return err + return 0, err } peerPublicKey, err := base64.StdEncoding.DecodeString(peerPublicKeyBase64) if err != nil { - return err + return 0, err } presharedKey, err := base64.StdEncoding.DecodeString(presharedKeyBase64) if err != nil { - return err + return 0, err } if presharedKeyBase64 == "" { @@ -148,7 +147,7 @@ func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKe ephemeral, err := ephemeralKeypair() if err != nil { - return err + return 0, err } cs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2s) @@ -165,7 +164,7 @@ func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKe Random: rand.Reader, }) if err != nil { - return err + return 0, err } // Prepare handshake initiation packet @@ -179,7 +178,7 @@ func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKe tai64nTimestampBuf = binary.BigEndian.AppendUint32(tai64nTimestampBuf, uint32(now.Nanosecond())) msg, _, _, err := hs.WriteMessage(nil, tai64nTimestampBuf) if err != nil { - return err + return 0, err } initiationPacket := new(bytes.Buffer) @@ -190,11 +189,11 @@ func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKe macKey := blake2s.Sum256(append([]byte("mac1----"), peerPublicKey...)) hasher, err := blake2s.New128(macKey[:]) // using macKey as the key if err != nil { - return err + return 0, err } _, err = hasher.Write(initiationPacket.Bytes()) if err != nil { - return err + return 0, err } initiationPacketMAC := hasher.Sum(nil) @@ -204,7 +203,7 @@ func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKe conn, err := net.Dial("udp", serverAddr.String()) if err != nil { - return err + return 0, err } defer conn.Close() @@ -216,36 +215,40 @@ func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKe randomPacket := make([]byte, packetSize) _, err := rand.Read(randomPacket) if err != nil { - return fmt.Errorf("error generating random packet: %w", err) + return 0, fmt.Errorf("error generating random packet: %w", err) } // Send the random packet _, err = conn.Write(randomPacket) if err != nil { - return fmt.Errorf("error sending random packet: %w", err) + 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) } - // spew.Dump(initiationPacket) _, err = initiationPacket.WriteTo(conn) if err != nil { - return err + return 0, err } + t0 := time.Now() response := make([]byte, 92) conn.SetReadDeadline(time.Now().Add(5 * time.Second)) i, err := conn.Read(response) - fmt.Println("server response, len+packet: ", i, response[12:60]) if err != nil { - return err + 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 errors.New("invalid response type") + return 0, errors.New("invalid response type") } // Extract sender and receiver index from the response @@ -254,23 +257,20 @@ func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKe // 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 errors.New("invalid sender index in response") + return 0, errors.New("invalid sender index in response") } payload, _, _, err := hs.ReadMessage(nil, response[12:60]) - spew.Dump(payload) if err != nil { - spew.Dump(err) - return err + return 0, err } // Check if the payload is empty (as expected in WireGuard handshake) if len(payload) != 0 { - return errors.New("unexpected payload in response") + return 0, errors.New("unexpected payload in response") } - fmt.Println("Handshake completed successfully") - return nil + return rtt, nil } func NewWarpPing(ip netip.Addr, opts *statute.ScannerOptions) *WarpPing { diff --git a/ipscanner/internal/statute/default.go b/ipscanner/internal/statute/default.go index 2c71910aa..24265a4a4 100644 --- a/ipscanner/internal/statute/default.go +++ b/ipscanner/internal/statute/default.go @@ -168,30 +168,6 @@ func DefaultQuicDialerFunc(ctx context.Context, addr string, _ *tls.Config, _ *q return quic.DialAddrEarly(ctx, addr, defaultTLSConfig(addr), quicConfig) } -// default logger - -type Logger interface { - Debug(s string, v ...interface{}) - Error(s string, v ...interface{}) -} - -type DefaultLogger struct{} - -func (l DefaultLogger) Debug(s string, v ...interface{}) { - fmt.Printf(fmt.Sprintf("%s\r\n", s), v...) -} - -func (l DefaultLogger) Error(s string, v ...interface{}) { - fmt.Printf(fmt.Sprintf("%s\r\n", s), v...) -} - -func DefaultIPQueueChangeCallback(ips []IPInfo) { - fmt.Printf("queue change: %d\r\n", len(ips)) - for _, ip := range ips { - fmt.Printf("IP:%s\tRTT:%d\tTS:%s\r\n", ip.IP.String(), ip.RTT, ip.CreatedAt.String()) - } -} - func DefaultCFRanges() []netip.Prefix { return []netip.Prefix{ netip.MustParsePrefix("103.21.244.0/22"), diff --git a/ipscanner/internal/statute/ping.go b/ipscanner/internal/statute/ping.go index 42702141d..816a26fc6 100644 --- a/ipscanner/internal/statute/ping.go +++ b/ipscanner/internal/statute/ping.go @@ -4,11 +4,10 @@ import ( "context" "crypto/tls" "fmt" - "net/netip" ) type IPingResult interface { - Result() int + Result() IPInfo Error() error fmt.Stringer } @@ -34,15 +33,3 @@ func TlsVersionToString(ver uint16) string { return "unknown" } } - -func IsIPv4(ip netip.Addr) bool { - return ip.Is4() -} - -func IsIPv6(ip netip.Addr) bool { - return ip.Is6() -} - -func CloneIP(ip netip.Addr) netip.Addr { - return ip -} diff --git a/ipscanner/internal/statute/statute.go b/ipscanner/internal/statute/statute.go index 19fe6bea7..4efe9ceeb 100644 --- a/ipscanner/internal/statute/statute.go +++ b/ipscanner/internal/statute/statute.go @@ -3,6 +3,7 @@ package statute import ( "context" "crypto/tls" + "log/slog" "net" "net/http" "net/netip" @@ -28,8 +29,8 @@ var ( ) type IPInfo struct { - IP netip.Addr - RTT int + AddrPort netip.AddrPort + RTT time.Duration CreatedAt time.Time } @@ -38,7 +39,7 @@ type ScannerOptions struct { UseIPv6 bool CidrList []netip.Prefix // CIDR ranges to scan SelectedOps int - Logger Logger + Logger *slog.Logger InsecureSkipVerify bool RawDialerFunc TDialerFunc TLSDialerFunc TDialerFunc @@ -57,7 +58,7 @@ type ScannerOptions struct { Port uint16 IPQueueSize int IPQueueTTL time.Duration - MaxDesirableRTT int + MaxDesirableRTT time.Duration IPQueueChangeCallback TIPQueueChangeCallback ConnectionTimeout time.Duration HandshakeTimeout time.Duration diff --git a/ipscanner/scanner.go b/ipscanner/scanner.go index 3da7343c3..04700fde4 100644 --- a/ipscanner/scanner.go +++ b/ipscanner/scanner.go @@ -1,7 +1,9 @@ package ipscanner import ( + "context" "crypto/tls" + "log/slog" "net/netip" "time" @@ -11,44 +13,42 @@ import ( type IPScanner struct { options statute.ScannerOptions - logger statute.Logger + log *slog.Logger engine *engine.Engine - // onChange func([]netip.Addr) } func NewScanner(options ...Option) *IPScanner { p := &IPScanner{ options: statute.ScannerOptions{ - UseIPv4: true, - UseIPv6: true, - CidrList: statute.DefaultCFRanges(), - SelectedOps: 0, - Logger: statute.DefaultLogger{}, - 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, - IPQueueTTL: 30 * time.Second, - IPQueueChangeCallback: statute.DefaultIPQueueChangeCallback, - ConnectionTimeout: 1 * time.Second, - HandshakeTimeout: 1 * time.Second, - TlsVersion: tls.VersionTLS13, + 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, }, - logger: statute.DefaultLogger{}, + log: slog.Default(), } for _, option := range options { @@ -132,8 +132,9 @@ func WithUserAgent(userAgent string) Option { } } -func WithLogger(logger statute.Logger) Option { +func WithLogger(logger *slog.Logger) Option { return func(i *IPScanner) { + i.log = logger i.options.Logger = logger } } @@ -198,7 +199,7 @@ func WithIPQueueSize(size int) Option { } } -func WithMaxDesirableRTT(threshold int) Option { +func WithMaxDesirableRTT(threshold time.Duration) Option { return func(i *IPScanner) { i.options.MaxDesirableRTT = threshold } @@ -210,12 +211,6 @@ func WithIPQueueTTL(ttl time.Duration) Option { } } -func WithIPQueueChangeCallback(callback statute.TIPQueueChangeCallback) Option { - return func(i *IPScanner) { - i.options.IPQueueChangeCallback = callback - } -} - func WithConnectionTimeout(timeout time.Duration) Option { return func(i *IPScanner) { i.options.ConnectionTimeout = timeout @@ -252,28 +247,20 @@ func WithWarpPreSharedKey(presharedKey string) Option { } } -func (i *IPScanner) SetIPQueueChangeCallback(callback statute.TIPQueueChangeCallback) { - i.options.IPQueueChangeCallback = callback -} - // 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() { +func (i *IPScanner) Run(ctx context.Context) { statute.FinalOptions = &i.options if !i.options.UseIPv4 && !i.options.UseIPv6 { - i.logger.Error("Fatal: both IPv4 and IPv6 are disabled, nothing to do") + i.log.Error("Fatal: both IPv4 and IPv6 are disabled, nothing to do") return } i.engine = engine.NewScannerEngine(&i.options) - go i.engine.Run() -} - -func (i *IPScanner) Stop() { - i.engine.Cancel() + go i.engine.Run(ctx) } -func (i *IPScanner) GetAvailableIPs() []netip.Addr { +func (i *IPScanner) GetAvailableIPs() []statute.IPInfo { if i.engine != nil { return i.engine.GetAvailableIPs(false) } diff --git a/wiresocks/scanner.go b/wiresocks/scanner.go index cbe58fba8..64f947555 100644 --- a/wiresocks/scanner.go +++ b/wiresocks/scanner.go @@ -25,7 +25,7 @@ func canConnectIPv6(remoteAddr netip.AddrPort) bool { return true } -func RunScan(ctx context.Context, rtt time.Duration) (result []netip.AddrPort, 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) @@ -44,34 +44,34 @@ func RunScan(ctx context.Context, rtt time.Duration) (result []netip.AddrPort, e ipscanner.WithWarpPeerPublicKey(publicKey), ipscanner.WithUseIPv6(canConnectIPv6(netip.MustParseAddrPort("[2001:4860:4860::8888]:80"))), ipscanner.WithUseIPv4(true), - ipscanner.WithMaxDesirableRTT(int(rtt.Milliseconds())), + 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, netip.AddrPortFrom(ipList[i], warp.RandomWarpPort())) - } - return result, nil - } - time.Sleep(1 * time.Second) // Prevent the loop from spinning too fast + case <-t.C: + // Prevent the loop from spinning too fast + continue } } } From 1282e950f53825207fa40bfaf3bfa1a148d7496e Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Tue, 12 Mar 2024 15:53:16 +0000 Subject: [PATCH 5/7] actions: use go1.21 Signed-off-by: Mark Pashmfouroush --- .github/workflows/go-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 11e3cb1e3af33c423f2ed4816e9be580e6c42b95 Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Wed, 13 Mar 2024 08:50:03 +0000 Subject: [PATCH 6/7] misc: more error cleanups Signed-off-by: Mark Pashmfouroush --- ipscanner/internal/iterator/iterator.go | 3 ++- ipscanner/internal/ping/http.go | 3 ++- ipscanner/internal/ping/quic.go | 3 ++- ipscanner/internal/ping/tls.go | 3 ++- psiphon/p.go | 4 ++-- warp/tls.go | 4 ++-- wiresocks/config.go | 7 ++++--- wiresocks/scanner.go | 3 ++- wiresocks/udpfw.go | 5 +++-- 9 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ipscanner/internal/iterator/iterator.go b/ipscanner/internal/iterator/iterator.go index 20627e4cc..0ec185d62 100644 --- a/ipscanner/internal/iterator/iterator.go +++ b/ipscanner/internal/iterator/iterator.go @@ -2,6 +2,7 @@ package iterator import ( "crypto/rand" + "errors" "fmt" "log" "math/big" @@ -229,7 +230,7 @@ func (g *IpGenerator) NextBatch() ([]netip.Addr, error) { } return g.NextBatch() } else { - return nil, fmt.Errorf("no more IP addresses") + return nil, errors.New("no more IP addresses") } } return results, nil diff --git a/ipscanner/internal/ping/http.go b/ipscanner/internal/ping/http.go index 295d8c467..11a4f5201 100644 --- a/ipscanner/internal/ping/http.go +++ b/ipscanner/internal/ping/http.go @@ -2,6 +2,7 @@ package ping import ( "context" + "errors" "fmt" "io" "net/http" @@ -57,7 +58,7 @@ func (h *HttpPing) PingContext(ctx context.Context) statute.IPingResult { orighost := u.Host if !h.IP.IsValid() { - return h.errorResult(fmt.Errorf("no IP specified")) + return h.errorResult(errors.New("no IP specified")) } req, err := http.NewRequestWithContext(ctx, h.Method, h.URL, nil) diff --git a/ipscanner/internal/ping/quic.go b/ipscanner/internal/ping/quic.go index a01439c66..92ca9e5e6 100644 --- a/ipscanner/internal/ping/quic.go +++ b/ipscanner/internal/ping/quic.go @@ -2,6 +2,7 @@ package ping import ( "context" + "errors" "fmt" "net/netip" "time" @@ -50,7 +51,7 @@ func (h *QuicPing) Ping() statute.IPingResult { func (h *QuicPing) PingContext(ctx context.Context) statute.IPingResult { if !h.IP.IsValid() { - return h.errorResult(fmt.Errorf("no IP specified")) + return h.errorResult(errors.New("no IP specified")) } addr := netip.AddrPortFrom(h.IP, h.Port) diff --git a/ipscanner/internal/ping/tls.go b/ipscanner/internal/ping/tls.go index b0aad1f4b..f2438a9a7 100644 --- a/ipscanner/internal/ping/tls.go +++ b/ipscanner/internal/ping/tls.go @@ -2,6 +2,7 @@ package ping import ( "context" + "errors" "fmt" "net/netip" "time" @@ -46,7 +47,7 @@ func (t *TlsPing) Ping() statute.IPingResult { func (t *TlsPing) PingContext(ctx context.Context) statute.IPingResult { if !t.IP.IsValid() { - return t.errorResult(fmt.Errorf("no IP specified")) + return t.errorResult(errors.New("no IP specified")) } addr := netip.AddrPortFrom(t.IP, t.Port) t0 := time.Now() diff --git a/psiphon/p.go b/psiphon/p.go index d82e02e81..4ea7fff64 100644 --- a/psiphon/p.go +++ b/psiphon/p.go @@ -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/warp/tls.go b/warp/tls.go index 0de1b204c..efcd9d74c 100644 --- a/warp/tls.go +++ b/warp/tls.go @@ -154,12 +154,12 @@ func (d *Dialer) makeTLSHelloPacketWithSNICurve(plainConn net.Conn, config *tls. } 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 diff --git a/wiresocks/config.go b/wiresocks/config.go index 905da8088..378d3e522 100644 --- a/wiresocks/config.go +++ b/wiresocks/config.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "encoding/hex" "errors" + "fmt" "net/netip" "strings" @@ -41,7 +42,7 @@ var ( 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 "", fmt.Errorf("%s should not be empty", keyName) } return key.String(), nil } @@ -62,10 +63,10 @@ func parseBase64KeyToHex(section *ini.Section, keyName string) (string, error) { 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 } diff --git a/wiresocks/scanner.go b/wiresocks/scanner.go index 64f947555..5a47dcf86 100644 --- a/wiresocks/scanner.go +++ b/wiresocks/scanner.go @@ -2,6 +2,7 @@ package wiresocks import ( "context" + "errors" "fmt" "net" "net/netip" @@ -68,7 +69,7 @@ func RunScan(ctx context.Context, rtt time.Duration) (result []ipscanner.IPInfo, select { case <-ctx.Done(): // Context is done - canceled externally - return nil, fmt.Errorf("user canceled the operation") + return nil, errors.New("user canceled the operation") case <-t.C: // Prevent the loop from spinning too fast continue 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 From c8c33049532f375240fd1515c992655da263e031 Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Thu, 14 Mar 2024 10:12:03 +0000 Subject: [PATCH 7/7] wireguard: lots of fixes Migrate "trick" to wireguard UAPI IPC. Refactor config parsing. Add tests for config parsing. Allow for keepalive to be configurable. Set keepalive to 3s for outer tunnel and 10s for inner. Add logs for trick mode. Redo MTU settings (needs more testing). Signed-off-by: Mark Pashmfouroush --- app/app.go | 40 ++++--- device/device.go | 5 +- device/device_test.go | 2 +- device/noise_test.go | 2 +- device/peer.go | 1 - device/send.go | 2 + device/uapi.go | 9 ++ go.mod | 6 +- go.sum | 15 ++- wiresocks/config.go | 223 +++++++++++++-------------------------- wiresocks/config_test.go | 82 ++++++++++++++ wiresocks/wiresocks.go | 41 +++---- 12 files changed, 222 insertions(+), 206 deletions(-) create mode 100644 wiresocks/config_test.go diff --git a/app/app.go b/app/app.go index 6e001d087..14c082242 100644 --- a/app/app.go +++ b/app/app.go @@ -16,6 +16,9 @@ import ( "github.com/bepass-org/wireguard-go/wiresocks" ) +const singleMTU = 1400 +const doubleMTU = 1320 + type WarpOptions struct { LogLevel string Bind netip.AddrPort @@ -88,34 +91,41 @@ func RunWarp(ctx context.Context, opts WarpOptions) error { warpErr = runWarpInWarp(ctx, opts.Bind, endpoints, opts.LogLevel == "debug") default: // just run primary warp on bindAddress - _, _, warpErr = runWarp(ctx, opts.Bind, endpoints, "./primary/wgcf-profile.ini", opts.LogLevel == "debug", true, true) + _, warpErr = runWarp(ctx, opts.Bind, endpoints, "./primary/wgcf-profile.ini", opts.LogLevel == "debug", true, true, singleMTU) } return warpErr } -func runWarp(ctx context.Context, bind netip.AddrPort, endpoints []string, confPath string, verbose, startProxy bool, trick bool) (*wiresocks.VirtualTun, int, error) { +func runWarp(ctx context.Context, bind netip.AddrPort, endpoints []string, confPath string, verbose, startProxy bool, trick bool, mtu int) (*wiresocks.VirtualTun, error) { conf, err := wiresocks.ParseConfig(confPath, endpoints[0]) if err != nil { log.Println(err) - return nil, 0, err + return nil, err } + conf.Interface.MTU = mtu + + for i, peer := range conf.Peers { + peer.KeepAlive = 10 + if trick { + peer.Trick = true + peer.KeepAlive = 3 + } - if trick { - conf.Device.Trick = trick + conf.Peers[i] = peer } - tnet, err := wiresocks.StartWireguard(ctx, conf.Device, verbose) + tnet, err := wiresocks.StartWireguard(ctx, conf, verbose) if err != nil { log.Println(err) - return nil, 0, err + return nil, err } if startProxy { tnet.StartProxy(bind) } - return tnet, conf.Device.MTU, nil + return tnet, nil } func runWarpWithPsiphon(ctx context.Context, bind netip.AddrPort, endpoints []string, country string, verbose bool) error { @@ -126,7 +136,7 @@ func runWarpWithPsiphon(ctx context.Context, bind netip.AddrPort, endpoints []st return err } - _, _, err = runWarp(ctx, warpBindAddress, endpoints, "./primary/wgcf-profile.ini", verbose, true, true) + _, err = runWarp(ctx, warpBindAddress, endpoints, "./primary/wgcf-profile.ini", verbose, true, true, singleMTU) if err != nil { return err } @@ -144,27 +154,27 @@ func runWarpWithPsiphon(ctx context.Context, bind netip.AddrPort, endpoints []st } func runWarpInWarp(ctx context.Context, bind netip.AddrPort, endpoints []string, verbose bool) error { - // run secondary warp - vTUN, mtu, err := runWarp(ctx, netip.AddrPort{}, endpoints, "./secondary/wgcf-profile.ini", verbose, false, true) + // Run outer warp + vTUN, err := runWarp(ctx, netip.AddrPort{}, endpoints, "./secondary/wgcf-profile.ini", verbose, false, true, singleMTU) if err != nil { return err } - // run virtual endpoint + // Run virtual endpoint virtualEndpointBindAddress, err := findFreePort("udp") if err != nil { log.Println("There are no free udp ports on Device!") return err } addr := endpoints[1] - err = wiresocks.NewVtunUDPForwarder(virtualEndpointBindAddress.String(), addr, vTUN, mtu+100, ctx) + err = wiresocks.NewVtunUDPForwarder(virtualEndpointBindAddress.String(), addr, vTUN, singleMTU, ctx) if err != nil { log.Println(err) return err } - // run primary warp - _, _, err = runWarp(ctx, bind, []string{virtualEndpointBindAddress.String()}, "./primary/wgcf-profile.ini", verbose, true, false) + // Run inner warp + _, err = runWarp(ctx, bind, []string{virtualEndpointBindAddress.String()}, "./primary/wgcf-profile.ini", verbose, true, false, doubleMTU) if err != nil { return err } diff --git a/device/device.go b/device/device.go index 526ed2da5..d3568ed1c 100644 --- a/device/device.go +++ b/device/device.go @@ -86,8 +86,6 @@ type Device struct { mtu atomic.Int32 } - trick bool - ipcMutex sync.RWMutex closed chan struct{} log *Logger @@ -283,9 +281,8 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error { return nil } -func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger, trick bool) *Device { +func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device { device := new(Device) - device.trick = trick device.state.state.Store(uint32(deviceStateDown)) device.closed = make(chan struct{}) device.log = logger diff --git a/device/device_test.go b/device/device_test.go index 8aa310b03..8b170a266 100644 --- a/device/device_test.go +++ b/device/device_test.go @@ -166,7 +166,7 @@ func genTestPair(tb testing.TB, realSocket bool) (pair testPair) { if _, ok := tb.(*testing.B); ok && !testing.Verbose() { level = LogLevelError } - p.dev = NewDevice(p.tun.TUN(), binds[i], NewLogger(level, fmt.Sprintf("dev%d: ", i)), false) + p.dev = NewDevice(p.tun.TUN(), binds[i], NewLogger(level, fmt.Sprintf("dev%d: ", i))) if err := p.dev.IpcSet(cfg[i]); err != nil { tb.Errorf("failed to configure device %d: %v", i, err) p.dev.Close() diff --git a/device/noise_test.go b/device/noise_test.go index 83c3420af..d6aae0851 100644 --- a/device/noise_test.go +++ b/device/noise_test.go @@ -39,7 +39,7 @@ func randDevice(t *testing.T) *Device { } tun := tuntest.NewChannelTUN() logger := NewLogger(LogLevelError, "") - device := NewDevice(tun.TUN(), conn.NewDefaultBind(), logger, false) + device := NewDevice(tun.TUN(), conn.NewDefaultBind(), logger) device.SetPrivateKey(sk) return device } diff --git a/device/peer.go b/device/peer.go index 3b84c02e2..616efdf7a 100644 --- a/device/peer.go +++ b/device/peer.go @@ -81,7 +81,6 @@ func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) { // create peer peer := new(Peer) peer.stopCh = make(chan int, 1) - peer.trick = true peer.cookieGenerator.Init(pk) peer.device = device peer.queue.outbound = newAutodrainingOutboundQueue(device) diff --git a/device/send.go b/device/send.go index 05479419d..f135b8242 100644 --- a/device/send.go +++ b/device/send.go @@ -121,6 +121,7 @@ func (peer *Peer) SendKeepalive() { if len(peer.queue.staged) == 0 && peer.isRunning.Load() { // Send some random packets on every keepalive if peer.trick { + peer.device.log.Verbosef("%v - Running tricks! (keepalive)", peer) peer.sendRandomPackets() } @@ -159,6 +160,7 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error { // send some random packets on handshake if peer.trick { + peer.device.log.Verbosef("%v - Running tricks! (handshake)", peer) peer.sendRandomPackets() } diff --git a/device/uapi.go b/device/uapi.go index 0a4ca0ade..108506fb8 100644 --- a/device/uapi.go +++ b/device/uapi.go @@ -119,6 +119,7 @@ func (device *Device) IpcGetOperation(w io.Writer) error { sendf("tx_bytes=%d", peer.txBytes.Load()) sendf("rx_bytes=%d", peer.rxBytes.Load()) sendf("persistent_keepalive_interval=%d", peer.persistentKeepaliveInterval.Load()) + sendf("trick=%t", peer.trick) device.allowedips.EntriesForPeer(peer, func(prefix netip.Prefix) bool { sendf("allowed_ip=%s", prefix.String()) @@ -386,6 +387,14 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error return ipcErrorf(ipc.IpcErrorInvalid, "invalid protocol version: %v", value) } + case "trick": + device.log.Verbosef("%v - UAPI: Setting trick: %s", peer.Peer, value) + parsedBool, err := strconv.ParseBool(value) + if err != nil { + return ipcErrorf(ipc.IpcErrorInvalid, "invalid trick value: %v", value) + } + peer.trick = parsedBool + default: return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI peer key: %v", key) } diff --git a/go.mod b/go.mod index ab91e1b1a..6a9193b0c 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,13 @@ go 1.21.1 replace github.com/Psiphon-Labs/psiphon-tunnel-core => github.com/bepass-org/psiphon-tunnel-core v0.0.0-20240311155012-9c2e10df08e5 require ( - github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/Psiphon-Labs/psiphon-tunnel-core v2.0.28+incompatible github.com/bepass-org/proxy v0.0.0-20240201095508-c86216dd0aea github.com/fatih/color v1.16.0 github.com/flynn/noise v1.1.0 + github.com/frankban/quicktest v1.14.6 github.com/go-ini/ini v1.67.0 + github.com/google/go-cmp v0.6.0 github.com/hashicorp/golang-lru v1.0.2 github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 github.com/quic-go/quic-go v0.40.1 @@ -48,6 +49,8 @@ require ( github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 // indirect github.com/juju/ratelimit v1.0.2 // indirect github.com/klauspost/compress v1.16.7 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -67,6 +70,7 @@ require ( github.com/refraction-networking/ed25519 v0.1.2 // indirect github.com/refraction-networking/gotapdance v1.7.10 // indirect github.com/refraction-networking/obfs4 v0.1.2 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect diff --git a/go.sum b/go.sum index 5769cd374..9fac1a6b6 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 h1:CVuXDbdzPW github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= -github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4= github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag= @@ -37,6 +35,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea h1:9C2rdYRp8Vzwhm3sbFX0yYfB+70zKFRjn7cnPCucHSw= github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea/go.mod h1:MdyNkAe06D7xmJsf+MsLvbZKYNXuOHLKJrvw+x4LlcQ= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,6 +59,8 @@ github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0 h1:7ZJyJV4Ki github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0/go.mod h1:2z3Tfqwv2ueuK6h563xUHRcCh1mv38wS9EjiWiesk84= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= @@ -80,6 +81,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= @@ -98,11 +100,13 @@ github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSg github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a h1:6SRny9FLB1eWasPyDUqBQnMi9NhXU01XIlB0ao89YoI= @@ -153,6 +157,7 @@ github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/ github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -177,6 +182,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rodaine/table v1.1.1 h1:zBliy3b4Oj6JRmncse2Z85WmoQvDrXOYuy0JXCt8Qz8= github.com/rodaine/table v1.1.1/go.mod h1:iqTRptjn+EVcrVBYtNMlJ2wrJZa3MpULUmcXFpfcziA= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 h1:ML7ZNtcln5UBo5Wv7RIv9Xg3Pr5VuRCWLFXEwda54Y4= diff --git a/wiresocks/config.go b/wiresocks/config.go index 378d3e522..0ddff54d8 100644 --- a/wiresocks/config.go +++ b/wiresocks/config.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/netip" - "strings" "github.com/go-ini/ini" ) @@ -14,50 +13,22 @@ import ( type PeerConfig struct { PublicKey string PreSharedKey string - Endpoint *string + Endpoint string KeepAlive int AllowedIPs []netip.Prefix + Trick bool } -// DeviceConfig contains the information to initiate a wireguard connection -type DeviceConfig struct { - SecretKey string - Endpoint []netip.Addr - Peers []PeerConfig +type InterfaceConfig struct { + PrivateKey string + Addresses []netip.Addr DNS []netip.Addr MTU int - ListenPort *int - Trick bool } type Configuration struct { - Device *DeviceConfig -} - -var ( - dnsAddresses = []string{"8.8.8.8", "8.8.4.4"} - dc = 0 -) - -func parseString(section *ini.Section, keyName string) (string, error) { - key := section.Key(strings.ToLower(keyName)) - if key == nil { - return "", fmt.Errorf("%s should not be empty", keyName) - } - return key.String(), nil -} - -func parseBase64KeyToHex(section *ini.Section, keyName string) (string, error) { - key, err := parseString(section, keyName) - if err != nil { - return "", err - } - result, err := encodeBase64ToHex(key) - if err != nil { - return result, err - } - - return result, nil + Interface *InterfaceConfig + Peers []PeerConfig } func encodeBase64ToHex(key string) (string, error) { @@ -71,140 +42,94 @@ func encodeBase64ToHex(key string) (string, error) { return hex.EncodeToString(decoded), nil } -func parseNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) { - key := section.Key(keyName) - if key == nil { - return []netip.Addr{}, nil - } - - var ips []netip.Addr - for _, str := range key.StringsWithShadows(",") { - str = strings.TrimSpace(str) - if str == "1.1.1.1" { - str = dnsAddresses[dc%len(dnsAddresses)] - dc++ - } - ip, err := netip.ParseAddr(str) - if err != nil { - return nil, err - } - ips = append(ips, ip) +// ParseInterface parses the [Interface] section +func ParseInterface(cfg *ini.File) (InterfaceConfig, error) { + device := InterfaceConfig{} + interfaces, err := cfg.SectionsByName("Interface") + if len(interfaces) != 1 || err != nil { + return InterfaceConfig{}, errors.New("only one [Interface] is expected") } - return ips, nil -} + iface := interfaces[0] -func parseCIDRNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) { - key := section.Key(keyName) + key := iface.Key("Address") if key == nil { - return []netip.Addr{}, nil + return InterfaceConfig{}, nil } - var ips []netip.Addr + var addresses []netip.Addr for _, str := range key.StringsWithShadows(",") { prefix, err := netip.ParsePrefix(str) if err != nil { - return nil, err + return InterfaceConfig{}, err } - addr := prefix.Addr() - ips = append(ips, addr) + addresses = append(addresses, prefix.Addr()) } - return ips, nil -} + device.Addresses = addresses -func parseAllowedIPs(section *ini.Section) ([]netip.Prefix, error) { - key := section.Key("AllowedIPs") + key = iface.Key("PrivateKey") if key == nil { - return []netip.Prefix{}, nil - } - - var ips []netip.Prefix - for _, str := range key.StringsWithShadows(",") { - prefix, err := netip.ParsePrefix(str) - if err != nil { - return nil, err - } - - ips = append(ips, prefix) - } - return ips, nil -} - -// ParseInterface parses the [Interface] section and extract the information into `device` -func ParseInterface(cfg *ini.File, device *DeviceConfig) error { - sections, err := cfg.SectionsByName("Interface") - if len(sections) != 1 || err != nil { - return errors.New("one and only one [Interface] is expected") - } - section := sections[0] - - address, err := parseCIDRNetIP(section, "Address") - if err != nil { - return err + return InterfaceConfig{}, errors.New("PrivateKey should not be empty") } - device.Endpoint = address - - privKey, err := parseBase64KeyToHex(section, "PrivateKey") + privateKeyHex, err := encodeBase64ToHex(key.String()) if err != nil { - return err + return InterfaceConfig{}, err } - device.SecretKey = privKey + device.PrivateKey = privateKeyHex - dns, err := parseNetIP(section, "DNS") - if err != nil { - return err + key = iface.Key("DNS") + if key == nil { + return InterfaceConfig{}, nil } - device.DNS = dns - if sectionKey, err := section.GetKey("MTU"); err == nil { - value, err := sectionKey.Int() + addresses = []netip.Addr{} + for _, str := range key.StringsWithShadows(",") { + ip, err := netip.ParseAddr(str) if err != nil { - return err - } - device.MTU = value - } else { - if dc == 0 { - device.MTU = 1420 - } else { - device.MTU = 1300 + return InterfaceConfig{}, err } + addresses = append(addresses, ip) } + device.DNS = addresses - if sectionKey, err := section.GetKey("ListenPort"); err == nil { + if sectionKey, err := iface.GetKey("MTU"); err == nil { value, err := sectionKey.Int() if err != nil { - return err + return InterfaceConfig{}, err } - device.ListenPort = &value + device.MTU = value } - return nil + return device, nil } // ParsePeers parses the [Peer] section and extract the information into `peers` -func ParsePeers(cfg *ini.File, peers *[]PeerConfig, endpoint string) error { +func ParsePeers(cfg *ini.File) ([]PeerConfig, error) { sections, err := cfg.SectionsByName("Peer") if len(sections) < 1 || err != nil { - return errors.New("at least one [Peer] is expected") + return nil, errors.New("at least one [Peer] is expected") } - for _, section := range sections { + peers := make([]PeerConfig, len(sections)) + for i, section := range sections { peer := PeerConfig{ PreSharedKey: "0000000000000000000000000000000000000000000000000000000000000000", KeepAlive: 0, } - decoded, err := parseBase64KeyToHex(section, "PublicKey") - if err != nil { - return err + if sectionKey, err := section.GetKey("PublicKey"); err == nil { + value, err := encodeBase64ToHex(sectionKey.String()) + if err != nil { + return nil, err + } + peer.PublicKey = value } - peer.PublicKey = decoded if sectionKey, err := section.GetKey("PreSharedKey"); err == nil { value, err := encodeBase64ToHex(sectionKey.String()) if err != nil { - return err + return nil, err } peer.PreSharedKey = value } @@ -212,21 +137,31 @@ func ParsePeers(cfg *ini.File, peers *[]PeerConfig, endpoint string) error { if sectionKey, err := section.GetKey("PersistentKeepalive"); err == nil { value, err := sectionKey.Int() if err != nil { - return err + return nil, err } peer.KeepAlive = value } - peer.AllowedIPs, err = parseAllowedIPs(section) - if err != nil { - return err + if sectionKey, err := section.GetKey("AllowedIPs"); err == nil { + var ips []netip.Prefix + for _, str := range sectionKey.StringsWithShadows(",") { + prefix, err := netip.ParsePrefix(str) + if err != nil { + return nil, err + } + ips = append(ips, prefix) + } + peer.AllowedIPs = ips } - peer.Endpoint = &endpoint + if sectionKey, err := section.GetKey("Endpoint"); err == nil { + peer.Endpoint = sectionKey.String() + } - *peers = append(*peers, peer) + peers[i] = peer } - return nil + + return peers, nil } // ParseConfig takes the path of a configuration file and parses it into Configuration @@ -242,31 +177,19 @@ func ParseConfig(path string, endpoint string) (*Configuration, error) { return nil, err } - device := &DeviceConfig{ - MTU: 1420, - } - - root := cfg.Section("") - wgConf, err := root.GetKey("WGConfig") - wgCfg := cfg - if err == nil { - wgCfg, err = ini.LoadSources(iniOpt, wgConf.String()) - if err != nil { - return nil, err - } - } - - err = ParseInterface(wgCfg, device) + iface, err := ParseInterface(cfg) if err != nil { return nil, err } - err = ParsePeers(wgCfg, &device.Peers, endpoint) + peers, err := ParsePeers(cfg) if err != nil { return nil, err } + for i, peer := range peers { + peer.Endpoint = endpoint + peers[i] = peer + } - return &Configuration{ - Device: device, - }, nil + return &Configuration{Interface: &iface, Peers: peers}, nil } diff --git a/wiresocks/config_test.go b/wiresocks/config_test.go new file mode 100644 index 000000000..3b4d62c54 --- /dev/null +++ b/wiresocks/config_test.go @@ -0,0 +1,82 @@ +package wiresocks + +import ( + "net/netip" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/go-ini/ini" + "github.com/google/go-cmp/cmp/cmpopts" +) + +const testConfig = ` +[Interface] +PrivateKey = aK8FWhiV1CtKFbKUPssL13P+Tv+c5owmYcU5PCP6yFw= +DNS = 8.8.8.8 +Address = 172.16.0.2/24 +Address = 2606:4700:110:8cc0:1ad3:9155:6742:ea8d/128 +[Peer] +PublicKey = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo= +AllowedIPs = 0.0.0.0/0 +AllowedIPs = ::/0 +Endpoint = engage.cloudflareclient.com:2408 +` +const ( + privateKeyBase64 = "68af055a1895d42b4a15b2943ecb0bd773fe4eff9ce68c2661c5393c23fac85c" + publicKeyBase64 = "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a" + presharedKeyBase64 = "0000000000000000000000000000000000000000000000000000000000000000" +) + +func TestParseInterface(t *testing.T) { + opts := ini.LoadOptions{ + Insensitive: true, + AllowShadows: true, + AllowNonUniqueSections: true, + } + + cfg, err := ini.LoadSources(opts, []byte(testConfig)) + qt.Assert(t, err, qt.IsNil) + + device, err := ParseInterface(cfg) + qt.Assert(t, err, qt.IsNil) + + want := InterfaceConfig{ + PrivateKey: privateKeyBase64, + Addresses: []netip.Addr{ + netip.MustParseAddr("172.16.0.2"), + netip.MustParseAddr("2606:4700:110:8cc0:1ad3:9155:6742:ea8d"), + }, + DNS: []netip.Addr{netip.MustParseAddr("8.8.8.8")}, + MTU: 0, + } + qt.Assert(t, device, qt.CmpEquals(cmpopts.EquateComparable(netip.Addr{})), want) + t.Logf("%+v", device) +} + +func TestParsePeers(t *testing.T) { + opts := ini.LoadOptions{ + Insensitive: true, + AllowShadows: true, + AllowNonUniqueSections: true, + } + + cfg, err := ini.LoadSources(opts, []byte(testConfig)) + qt.Assert(t, err, qt.IsNil) + + peers, err := ParsePeers(cfg) + qt.Assert(t, err, qt.IsNil) + + want := []PeerConfig{{ + PublicKey: publicKeyBase64, + PreSharedKey: presharedKeyBase64, + Endpoint: "engage.cloudflareclient.com:2408", + KeepAlive: 0, + AllowedIPs: []netip.Prefix{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + Trick: false, + }} + qt.Assert(t, peers, qt.CmpEquals(cmpopts.EquateComparable(netip.Prefix{})), want) + t.Logf("%+v", peers) +} diff --git a/wiresocks/wiresocks.go b/wiresocks/wiresocks.go index 2acddee6e..c8898d337 100644 --- a/wiresocks/wiresocks.go +++ b/wiresocks/wiresocks.go @@ -6,7 +6,6 @@ import ( "fmt" "net/netip" - "github.com/MakeNowJust/heredoc/v2" "github.com/bepass-org/wireguard-go/conn" "github.com/bepass-org/wireguard-go/device" "github.com/bepass-org/wireguard-go/tun/netstack" @@ -21,45 +20,29 @@ type DeviceSetting struct { } // serialize the config into an IPC request and DeviceSetting -func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) { +func createIPCRequest(conf *Configuration) (*DeviceSetting, error) { var request bytes.Buffer - request.WriteString(fmt.Sprintf("private_key=%s\n", conf.SecretKey)) - - if conf.ListenPort != nil { - request.WriteString(fmt.Sprintf("listen_port=%d\n", *conf.ListenPort)) - } + request.WriteString(fmt.Sprintf("private_key=%s\n", conf.Interface.PrivateKey)) for _, peer := range conf.Peers { - request.WriteString(fmt.Sprintf(heredoc.Doc(` - public_key=%s - persistent_keepalive_interval=%d - preshared_key=%s - `), - peer.PublicKey, 1, peer.PreSharedKey, - )) - if peer.Endpoint != nil { - request.WriteString(fmt.Sprintf("endpoint=%s\n", *peer.Endpoint)) - } + request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey)) + request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive)) + request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey)) + request.WriteString(fmt.Sprintf("endpoint=%s\n", peer.Endpoint)) + request.WriteString(fmt.Sprintf("trick=%t\n", peer.Trick)) - if len(peer.AllowedIPs) > 0 { - for _, ip := range peer.AllowedIPs { - request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip.String())) - } - } else { - request.WriteString(heredoc.Doc(` - allowed_ip=0.0.0.0/0 - allowed_ip=::0/0 - `)) + for _, cidr := range peer.AllowedIPs { + request.WriteString(fmt.Sprintf("allowed_ip=%s\n", cidr)) } } - setting := &DeviceSetting{ipcRequest: request.String(), dns: conf.DNS, deviceAddr: conf.Endpoint, mtu: conf.MTU} + setting := &DeviceSetting{ipcRequest: request.String(), dns: conf.Interface.DNS, deviceAddr: conf.Interface.Addresses, mtu: conf.Interface.MTU} return setting, nil } // StartWireguard creates a tun interface on netstack given a configuration -func StartWireguard(ctx context.Context, conf *DeviceConfig, verbose bool) (*VirtualTun, error) { +func StartWireguard(ctx context.Context, conf *Configuration, verbose bool) (*VirtualTun, error) { setting, err := createIPCRequest(conf) if err != nil { return nil, err @@ -75,7 +58,7 @@ func StartWireguard(ctx context.Context, conf *DeviceConfig, verbose bool) (*Vir logLevel = device.LogLevelSilent } - dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(logLevel, ""), conf.Trick) + dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(logLevel, "")) err = dev.IpcSet(setting.ipcRequest) if err != nil { return nil, err