diff --git a/app/app.go b/app/app.go index 0966eb9b9..b64d23b5a 100644 --- a/app/app.go +++ b/app/app.go @@ -3,76 +3,91 @@ package app import ( "context" "errors" - "flag" "fmt" - "github.com/bepass-org/wireguard-go/psiphon" - "github.com/bepass-org/wireguard-go/warp" - "github.com/bepass-org/wireguard-go/wiresocks" "log" "net" + "net/netip" "os" "path/filepath" "time" + + "github.com/bepass-org/wireguard-go/psiphon" + "github.com/bepass-org/wireguard-go/warp" + "github.com/bepass-org/wireguard-go/wiresocks" ) -func RunWarp(psiphonEnabled, gool, scan, verbose bool, country, bindAddress, endpoint, license string, ctx context.Context, rtThreshold int) error { - // check if user input is not correct - if (psiphonEnabled && gool) || (!psiphonEnabled && country != "") { - log.Println("Wrong combination of flags!") - flag.Usage() - return errors.New("wrong command") +type WarpOptions struct { + LogLevel string + Bind netip.AddrPort + Endpoint string + License string + Psiphon *PsiphonOptions + Gool bool + Scan *ScanOptions +} + +type PsiphonOptions struct { + Country string +} + +type ScanOptions struct { + MaxRTT time.Duration +} + +func RunWarp(ctx context.Context, opts WarpOptions) error { + if opts.Psiphon != nil && opts.Gool { + return errors.New("can't use psiphon and gool at the same time") + } + + if opts.Psiphon != nil && opts.Psiphon.Country == "" { + return errors.New("must provide country for psiphon") } //create necessary file structures if err := makeDirs(); err != nil { return err } + log.Println("'primary' and 'secondary' directories are ready") // Change the current working directory to 'stuff' if err := os.Chdir("stuff"); err != nil { - log.Printf("Error changing to 'stuff' directory: %v\n", err) - return fmt.Errorf("Error changing to 'stuff' directory: %v\n", err) + return fmt.Errorf("error changing to 'stuff' directory: %w", err) } log.Println("Changed working directory to 'stuff'") - defer func() { - // back where you where - if err := os.Chdir(".."); err != nil { - log.Fatal("Error changing to 'main' directory:", err) - } - }() //create identities - if err := createPrimaryAndSecondaryIdentities(license); err != nil { + if err := createPrimaryAndSecondaryIdentities(opts.License); err != nil { return err } //Decide Working Scenario - endpoints := []string{endpoint, endpoint} + endpoints := []string{opts.Endpoint, opts.Endpoint} - if scan { + if opts.Scan != nil { var err error - endpoints, err = wiresocks.RunScan(&ctx, rtThreshold) + endpoints, err = wiresocks.RunScan(&ctx, opts.Scan.MaxRTT) if err != nil { return err } } - if !psiphonEnabled && !gool { - // just run primary warp on bindAddress - _, _, err := runWarp(bindAddress, endpoints, "./primary/wgcf-profile.ini", verbose, true, ctx, true) - return err - } else if psiphonEnabled && !gool { + var warpErr error + switch { + case opts.Psiphon != nil: // run primary warp on a random tcp port and run psiphon on bind address - return runWarpWithPsiphon(bindAddress, endpoints, country, verbose, ctx) - } else if !psiphonEnabled && gool { + warpErr = runWarpWithPsiphon(opts.Bind, endpoints, opts.Psiphon.Country, opts.LogLevel == "debug", ctx) + case opts.Gool: // run warp in warp - return runWarpInWarp(bindAddress, endpoints, verbose, ctx) + warpErr = runWarpInWarp(opts.Bind, endpoints, opts.LogLevel == "debug", ctx) + default: + // just run primary warp on bindAddress + _, _, warpErr = runWarp(opts.Bind, endpoints, "./primary/wgcf-profile.ini", opts.LogLevel == "debug", true, ctx) } - return fmt.Errorf("unknown error, it seems core related issue") + return warpErr } -func runWarp(bindAddress string, endpoints []string, confPath string, verbose, startProxy bool, ctx context.Context, showServing bool) (*wiresocks.VirtualTun, int, error) { +func runWarp(bind netip.AddrPort, endpoints []string, confPath string, verbose, startProxy bool, ctx context.Context) (*wiresocks.VirtualTun, int, error) { conf, err := wiresocks.ParseConfig(confPath, endpoints[0]) if err != nil { log.Println(err) @@ -86,17 +101,13 @@ func runWarp(bindAddress string, endpoints []string, confPath string, verbose, s } if startProxy { - tnet.StartProxy(bindAddress) - } - - if showServing { - log.Printf("Serving on %s\n", bindAddress) + tnet.StartProxy(bind) } return tnet, conf.Device.MTU, nil } -func runWarpWithPsiphon(bindAddress string, endpoints []string, country string, verbose bool, ctx context.Context) error { +func runWarpWithPsiphon(bind netip.AddrPort, endpoints []string, country string, verbose bool, ctx context.Context) error { // make a random bind address for warp warpBindAddress, err := findFreePort("tcp") if err != nil { @@ -104,26 +115,26 @@ func runWarpWithPsiphon(bindAddress string, endpoints []string, country string, return err } - _, _, err = runWarp(warpBindAddress, endpoints, "./primary/wgcf-profile.ini", verbose, true, ctx, false) + _, _, err = runWarp(warpBindAddress, endpoints, "./primary/wgcf-profile.ini", verbose, true, ctx) if err != nil { return err } // run psiphon - err = psiphon.RunPsiphon(warpBindAddress, bindAddress, country, ctx) + 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) } - log.Printf("Serving on %s\n", bindAddress) + log.Printf("Serving on %s", bind) return nil } -func runWarpInWarp(bindAddress string, endpoints []string, verbose bool, ctx context.Context) error { +func runWarpInWarp(bind netip.AddrPort, endpoints []string, verbose bool, ctx context.Context) error { // run secondary warp - vTUN, mtu, err := runWarp("", endpoints, "./secondary/wgcf-profile.ini", verbose, false, ctx, false) + vTUN, mtu, err := runWarp(netip.AddrPort{}, endpoints, "./secondary/wgcf-profile.ini", verbose, false, ctx) if err != nil { return err } @@ -135,60 +146,58 @@ func runWarpInWarp(bindAddress string, endpoints []string, verbose bool, ctx con return err } addr := endpoints[1] - if addr == "notset" { - addr, _ = wiresocks.ResolveIPPAndPort("engage.cloudflareclient.com:2408") + if addr == "" { + warpEndpoint, err := warp.RandomWarpEndpoint() + if err != nil { + return err + } + addr = warpEndpoint.String() } - err = wiresocks.NewVtunUDPForwarder(virtualEndpointBindAddress, addr, vTUN, mtu+100, ctx) + err = wiresocks.NewVtunUDPForwarder(virtualEndpointBindAddress.String(), addr, vTUN, mtu+100, ctx) if err != nil { log.Println(err) return err } // run primary warp - _, _, err = runWarp(bindAddress, []string{virtualEndpointBindAddress}, "./primary/wgcf-profile.ini", verbose, true, ctx, true) + _, _, err = runWarp(bind, []string{virtualEndpointBindAddress.String()}, "./primary/wgcf-profile.ini", verbose, true, ctx) if err != nil { return err } return nil } -func findFreePort(network string) (string, error) { +func findFreePort(network string) (netip.AddrPort, error) { if network == "udp" { addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") if err != nil { - return "", err + return netip.AddrPort{}, err } conn, err := net.ListenUDP("udp", addr) if err != nil { - return "", err + return netip.AddrPort{}, err } defer conn.Close() - return conn.LocalAddr().(*net.UDPAddr).String(), nil + return netip.MustParseAddrPort(conn.LocalAddr().String()), nil } // Listen on TCP port 0, which tells the OS to pick a free port. listener, err := net.Listen(network, "127.0.0.1:0") if err != nil { - return "", err // Return error if unable to listen on a port + return netip.AddrPort{}, err // Return error if unable to listen on a port } defer listener.Close() // Ensure the listener is closed when the function returns // Get the port from the listener's address - addr := listener.Addr().String() - - return addr, nil + return netip.MustParseAddrPort(listener.Addr().String()), nil } func createPrimaryAndSecondaryIdentities(license string) error { // make primary identity - _license := license - if _license == "notset" { - _license = "" - } warp.UpdatePath("./primary") if !warp.CheckProfileExists(license) { - err := warp.LoadOrCreateIdentity(_license) + err := warp.LoadOrCreateIdentity(license) if err != nil { log.Printf("error: %v", err) return fmt.Errorf("error: %v", err) @@ -197,7 +206,7 @@ func createPrimaryAndSecondaryIdentities(license string) error { // make secondary warp.UpdatePath("./secondary") if !warp.CheckProfileExists(license) { - err := warp.LoadOrCreateIdentity(_license) + err := warp.LoadOrCreateIdentity(license) if err != nil { log.Printf("error: %v", err) return fmt.Errorf("error: %v", err) @@ -213,54 +222,19 @@ func makeDirs() error { // Check if 'stuff' directory exists, if not create it if _, err := os.Stat(stuffDir); os.IsNotExist(err) { - log.Println("'stuff' directory does not exist, creating it...") if err := os.Mkdir(stuffDir, 0755); err != nil { - log.Println("Error creating 'stuff' directory:", err) - return errors.New("Error creating 'stuff' directory:" + err.Error()) + return fmt.Errorf("error creating 'stuff' directory: %w", err) } } // 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) { - log.Printf("Creating '%s' directory...\n", dir) if err := os.Mkdir(filepath.Join(stuffDir, dir), 0755); err != nil { - log.Printf("Error creating '%s' directory: %v\n", dir, err) - return fmt.Errorf("Error creating '%s' directory: %v\n", dir, err) + return fmt.Errorf("error creating '%s' directory: %w", dir, err) } } } - log.Println("'primary' and 'secondary' directories are ready") - return nil -} -func isPortOpen(address string, timeout time.Duration) bool { - // Try to establish a connection - conn, err := net.DialTimeout("tcp", address, timeout) - if err != nil { - return false - } - defer conn.Close() - return true -} - -func waitForPortToGetsOpenOrTimeout(addressToCheck string) { - timeout := 5 * time.Second - checkInterval := 500 * time.Millisecond - - // Set a deadline for when to stop checking - deadline := time.Now().Add(timeout) - - for { - if time.Now().After(deadline) { - log.Fatalf("Timeout reached, port %s is not open", addressToCheck) - } - - if isPortOpen(addressToCheck, checkInterval) { - log.Printf("Port %s is now open", addressToCheck) - break - } - - time.Sleep(checkInterval) - } + return nil } diff --git a/go.mod b/go.mod index 4e638d249..535bfc02b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/bepass-org/ipscanner v0.0.0-20240205155121-8927b7437d16 github.com/bepass-org/proxy v0.0.0-20240201095508-c86216dd0aea github.com/go-ini/ini v1.67.0 + github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 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 diff --git a/go.sum b/go.sum index 35c1c17ea..f49965b1a 100644 --- a/go.sum +++ b/go.sum @@ -111,6 +111,9 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/pebbe/zmq4 v1.2.10 h1:wQkqRZ3CZeABIeidr3e8uQZMMH5YAykA/WN0L5zkd1c= 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/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= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -248,6 +251,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.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= diff --git a/main.go b/main.go index 859a6f432..fd52f23c5 100644 --- a/main.go +++ b/main.go @@ -2,150 +2,126 @@ package main import ( "context" - "flag" + "errors" "fmt" "log" - "net" + "net/netip" "os" "os/signal" "syscall" + "time" "github.com/bepass-org/wireguard-go/app" -) - -type Flags struct { - Verbose bool - BindAddress string - Endpoint string - License string - Country string - PsiphonEnabled bool - Gool bool - Scan bool - Rtt int -} - -var validFlags = map[string]bool{ - "-v": true, - "-b": true, - "-e": true, - "-k": true, - "-country": true, - "-cfon": true, - "-gool": true, - "-scan": true, - "-rtt": true, -} - -func newFlags() *Flags { - return &Flags{} -} + "github.com/bepass-org/wireguard-go/warp" -func (f *Flags) setup() { - flag.BoolVar(&f.Verbose, "v", false, "verbose") - flag.StringVar(&f.BindAddress, "b", "127.0.0.1:8086", "socks bind address") - flag.StringVar(&f.Endpoint, "e", "notset", "warp clean IP") - flag.StringVar(&f.License, "k", "notset", "license key") - flag.StringVar(&f.Country, "country", "", "psiphon country code in ISO 3166-1 alpha-2 format") - flag.BoolVar(&f.PsiphonEnabled, "cfon", false, "enable Psiphon over warp") - flag.BoolVar(&f.Gool, "gool", false, "enable warp gooling") - flag.BoolVar(&f.Scan, "scan", false, "enable warp scanner(experimental)") - flag.IntVar(&f.Rtt, "rtt", 1000, "scanner rtt threshold, default 1000") - - flag.Usage = usage - flag.Parse() -} - -var validCountryCodes = map[string]bool{ - "AT": true, - "BE": true, - "BG": true, - "BR": true, - "CA": true, - "CH": true, - "CZ": true, - "DE": true, - "DK": true, - "EE": true, - "ES": true, - "FI": true, - "FR": true, - "GB": true, - "HU": true, - "IE": true, - "IN": true, - "IT": true, - "JP": true, - "LV": true, - "NL": true, - "NO": true, - "PL": true, - "RO": true, - "RS": true, - "SE": true, - "SG": true, - "SK": true, - "UA": true, - "US": true, -} + "github.com/peterbourgon/ff/v4" + "github.com/peterbourgon/ff/v4/ffhelp" +) -func usage() { - log.Println("./warp-plus-go [-v] [-b addr:port] [-c config-file-path] [-e warp-ip] [-k license-key] [-country country-code] [-cfon] [-gool]") - flag.PrintDefaults() +var psiphonCountries = []string{ + "AT", + "BE", + "BG", + "BR", + "CA", + "CH", + "CZ", + "DE", + "DK", + "EE", + "ES", + "FI", + "FR", + "GB", + "HU", + "IE", + "IN", + "IT", + "JP", + "LV", + "NL", + "NO", + "PL", + "RO", + "RS", + "SE", + "SG", + "SK", + "UA", + "US", } -func validateFlags(f *Flags) error { - if _, err := net.ResolveTCPAddr("tcp", f.BindAddress); err != nil { - return fmt.Errorf("invalid bindAddress format: %s", f.BindAddress) +func main() { + fs := ff.NewFlagSet("warp") + var ( + verbose = fs.Bool('v', "verbose", "enable verbose logging") + bind = fs.String('b', "bind", "127.0.0.1:8086", "socks bind address") + endpoint = fs.String('e', "endpoint", "", "warp endpoint") + key = fs.String('k', "key", "", "warp key") + country = fs.StringEnumLong("country", fmt.Sprintf("psiphon country code (valid values: %s)", psiphonCountries), psiphonCountries...) + psiphon = fs.BoolLong("cfon", "enable psiphon mode (must provide country as well)") + gool = fs.BoolLong("gool", "enable gool mode (warp in warp)") + scan = fs.BoolLong("scan", "enable warp scanning (experimental)") + rtt = fs.DurationLong("rtt", 1000*time.Millisecond, "scanner rtt limit") + ) + + // Config file and envvars can be added through ff later + err := ff.Parse(fs, os.Args[1:]) + switch { + case errors.Is(err, ff.ErrHelp): + fmt.Fprintf(os.Stderr, "%s\n", ffhelp.Flags(fs)) + os.Exit(0) + case err != nil: + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) } - if ip := net.ParseIP(f.Endpoint); ip == nil { - return fmt.Errorf("invalid warp clean IP: %s", f.Endpoint) + if *psiphon && *gool { + log.Fatal(errors.New("can't use cfon and gool at the same time")) } - if f.PsiphonEnabled && f.Country == "" { - return fmt.Errorf("if Psiphon is enabled, country code must be provided") + bindAddrPort, err := netip.ParseAddrPort(*bind) + if err != nil { + log.Fatal(fmt.Errorf("invalid bind address: %w", err)) } - if !validCountryCodes[f.Country] { - validCountries := make([]string, 0, len(validCountryCodes)) - - for code, _ := range validCountryCodes { - validCountries = append(validCountries, code) - } - - return fmt.Errorf("invalid country code: %s. Valid country codes: $s", f.Country, validCountries) + opts := app.WarpOptions{ + Bind: bindAddrPort, + Endpoint: *endpoint, + License: *key, + Gool: *gool, } - return nil -} - -func main() { - // Check for unexpected flags - for _, arg := range os.Args[1:] { - if !validFlags[arg] { - log.Fatalf("Invalid flag: %s", arg) - } + if *verbose { + opts.LogLevel = "debug" + log.Printf("setting log level to: %s", opts.LogLevel) } - flags := newFlags() - flags.setup() + if *psiphon { + log.Printf("psiphon mode enabled, using country %s", *country) + opts.Psiphon = &app.PsiphonOptions{Country: *country} + } - if err := validateFlags(flags); err != nil { - log.Fatalf("Validatrion error: %v", err) + if *scan { + log.Printf("scanner mode enabled, using %s max RTT", rtt) + opts.Scan = &app.ScanOptions{MaxRTT: *rtt} } - sigchan := make(chan os.Signal) - signal.Notify(sigchan, os.Interrupt, syscall.SIGTERM) - ctx, cancel := context.WithCancel(context.Background()) + // If the endpoint is not set, choose a random warp endpoint + if opts.Endpoint == "" { + addrPort, err := warp.RandomWarpEndpoint() + if err != nil { + log.Fatal(err) + } + opts.Endpoint = addrPort.String() + } + ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) go func() { - err := app.RunWarp(flags.PsiphonEnabled, flags.Gool, flags.Scan, flags.Verbose, flags.Country, flags.BindAddress, flags.Endpoint, flags.License, ctx, flags.Rtt) - if err != nil { + if err := app.RunWarp(ctx, opts); err != nil { log.Fatal(err) } }() - <-sigchan - cancel() + <-ctx.Done() } diff --git a/warp/endpoint.go b/warp/endpoint.go new file mode 100644 index 000000000..7d284c1f8 --- /dev/null +++ b/warp/endpoint.go @@ -0,0 +1,36 @@ +package warp + +import ( + "math/rand" + "net/netip" + "time" +) + +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 + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + + // Pick a random port number + randomPort := uint16(ports[rng.Intn(len(ports))]) + + cidrs := []netip.Prefix{ + netip.MustParsePrefix("162.159.195.0/24"), + netip.MustParsePrefix("188.114.96.0/24"), + netip.MustParsePrefix("162.159.192.0/24"), + netip.MustParsePrefix("188.114.97.0/24"), + netip.MustParsePrefix("188.114.99.0/24"), + netip.MustParsePrefix("188.114.98.0/24"), + } + + randomIP, err := RandomIPFromPrefix(cidrs[rng.Intn(len(cidrs))]) + if err != nil { + return netip.AddrPort{}, err + } + + return netip.AddrPortFrom(randomIP, randomPort), nil +} diff --git a/warp/tls.go b/warp/tls.go index f41a4c438..ce90313c4 100644 --- a/warp/tls.go +++ b/warp/tls.go @@ -1,11 +1,16 @@ package warp import ( - "crypto/rand" + "errors" "fmt" - tls "github.com/refraction-networking/utls" "io" + "math/big" + "math/rand" "net" + "net/netip" + "time" + + tls "github.com/refraction-networking/utls" ) var previousIP string @@ -164,44 +169,50 @@ func (d *Dialer) makeTLSHelloPacketWithSNICurve(plainConn net.Conn, config *tls. return utlsConn, nil } -func RandomIPFromRange(cidr string) (net.IP, error) { - -GENERATE: +// 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") + } - ip, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return nil, err + prefixLen := cidr.Bits() + if prefixLen == -1 { + return netip.Addr{}, fmt.Errorf("invalid cidr: %s", cidr) } - // The number of leading 1s in the mask - ones, _ := ipnet.Mask.Size() - quotient := ones / 8 - remainder := ones % 8 + // Initialise rand number generator + rng := rand.New(rand.NewSource(time.Now().UnixNano())) - // create random 4-byte byte slice - r := make([]byte, 4) - _, err = rand.Read(r) - if err != nil { - return nil, err - } + // Find the bit length of the Host portion of the provided CIDR + // prefix + hostLen := big.NewInt(int64(startingAddress.BitLen() - prefixLen)) - for i := 0; i <= quotient; i++ { - if i == quotient { - shifted := byte(r[i]) >> remainder - r[i] = ^ipnet.IP[i] & shifted - } else { - r[i] = ipnet.IP[i] - } - } - ip = net.IPv4(r[0], r[1], r[2], r[3]) + // 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) - if ip.Equal(ipnet.IP) || r[3] == 255 || ip.String() == previousIP { - // we got unlucky. The host portion of our ipv4 address was - // either all 0s (the network address) or all 1s (the broadcast address) - goto GENERATE + // 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) } - previousIP = ip.String() - return ip, nil + + // Unmap any mapped v4 addresses before return + return randomAddress.Unmap(), nil } // TLSDial dials a TLS connection. @@ -210,7 +221,7 @@ func (d *Dialer) TLSDial(plainDialer *net.Dialer, network, addr string) (net.Con if err != nil { return nil, err } - ip, err := RandomIPFromRange("141.101.113.0/24") + ip, err := 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 a80236e0a..e83ef6097 100644 --- a/wiresocks/config.go +++ b/wiresocks/config.go @@ -4,12 +4,7 @@ import ( "encoding/base64" "encoding/hex" "errors" - "fmt" - "github.com/bepass-org/wireguard-go/warp" - "math/rand" - "net" "strings" - "time" "github.com/go-ini/ini" @@ -134,46 +129,6 @@ func parseAllowedIPs(section *ini.Section) ([]netip.Prefix, error) { return ips, nil } -func resolveIP(ip string) (*net.IPAddr, error) { - return net.ResolveIPAddr("ip", ip) -} - -func ResolveIPPAndPort(addr string) (string, error) { - if addr == "engage.cloudflareclient.com:2408" { - // Define your specific list of port numbers - 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 - rand.Seed(time.Now().UnixNano()) - - // Pick a random port number - randomPort := ports[rand.Intn(len(ports))] - - cidrs := []string{ - "162.159.195.0/24", "188.114.96.0/24", "162.159.192.0/24", - "188.114.97.0/24", "188.114.99.0/24", "188.114.98.0/24", - } - - ip, err := warp.RandomIPFromRange(cidrs[rand.Intn(len(cidrs))]) - if err == nil { - return fmt.Sprintf("%s:%d", ip.String(), randomPort), nil - } - } - host, port, err := net.SplitHostPort(addr) - if err != nil { - return "", err - } - - ip, err := resolveIP(host) - if err != nil { - return "", err - } - return net.JoinHostPort(ip.String(), port), 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") @@ -253,19 +208,6 @@ func ParsePeers(cfg *ini.File, peers *[]PeerConfig, endpoint string) error { peer.PreSharedKey = value } - if sectionKey, err := section.GetKey("Endpoint"); err == nil { - value := sectionKey.String() - if endpoint != "notset" { - peer.Endpoint = &endpoint - } else { - decoded, err = ResolveIPPAndPort(strings.ToLower(value)) - if err != nil { - return err - } - peer.Endpoint = &decoded - } - } - if sectionKey, err := section.GetKey("PersistentKeepalive"); err == nil { value, err := sectionKey.Int() if err != nil { @@ -279,6 +221,8 @@ func ParsePeers(cfg *ini.File, peers *[]PeerConfig, endpoint string) error { return err } + peer.Endpoint = &endpoint + *peers = append(*peers, peer) } return nil diff --git a/wiresocks/proxy.go b/wiresocks/proxy.go index d13f271cc..634ccbd99 100644 --- a/wiresocks/proxy.go +++ b/wiresocks/proxy.go @@ -2,14 +2,15 @@ package wiresocks import ( "context" - "fmt" + "io" + "log" + "net/netip" + "time" + "github.com/bepass-org/proxy/pkg/mixed" "github.com/bepass-org/proxy/pkg/statute" "github.com/bepass-org/wireguard-go/device" "github.com/bepass-org/wireguard-go/tun/netstack" - "io" - "log" - "time" ) // VirtualTun stores a reference to netstack network and DNS configuration @@ -37,9 +38,9 @@ func (l DefaultLogger) Error(v ...interface{}) { } // StartProxy spawns a socks5 server. -func (vt *VirtualTun) StartProxy(bindAddress string) { +func (vt *VirtualTun) StartProxy(bindAddress netip.AddrPort) { proxy := mixed.NewProxy( - mixed.WithBindAddress(bindAddress), + mixed.WithBindAddress(bindAddress.String()), mixed.WithLogger(vt.Logger), mixed.WithContext(vt.Ctx), mixed.WithUserHandler(func(request *statute.ProxyRequest) error { @@ -64,7 +65,7 @@ func (vt *VirtualTun) StartProxy(bindAddress string) { func (vt *VirtualTun) generalHandler(req *statute.ProxyRequest) error { if vt.Verbose { - log.Println(fmt.Sprintf("handling %s request to %s", req.Network, req.Destination)) + log.Printf("handling %s request to %s", req.Network, req.Destination) } conn, err := vt.Tnet.Dial(req.Network, req.Destination) if err != nil { diff --git a/wiresocks/scanner.go b/wiresocks/scanner.go index d9010c6d9..34dd221b0 100644 --- a/wiresocks/scanner.go +++ b/wiresocks/scanner.go @@ -4,12 +4,13 @@ import ( "context" "crypto/rand" "fmt" - "github.com/bepass-org/ipscanner" - "github.com/go-ini/ini" "log" "net" "strings" "time" + + "github.com/bepass-org/ipscanner" + "github.com/go-ini/ini" ) func canConnectIPv6(remoteAddr string) bool { @@ -25,7 +26,7 @@ func canConnectIPv6(remoteAddr string) bool { return true } -func RunScan(ctx *context.Context, rtt int) (result []string, err error) { +func RunScan(ctx *context.Context, rtt time.Duration) (result []string, err error) { cfg, err := ini.Load("./primary/wgcf-profile.ini") if err != nil { log.Printf("Failed to read file: %v", err) @@ -45,7 +46,7 @@ func RunScan(ctx *context.Context, rtt int) (result []string, err error) { ipscanner.WithWarpPeerPublicKey(publicKey), ipscanner.WithUseIPv6(canConnectIPv6("[2001:4860:4860::8888]:80")), ipscanner.WithUseIPv4(true), - ipscanner.WithMaxDesirableRTT(rtt), + ipscanner.WithMaxDesirableRTT(int(rtt.Milliseconds())), ipscanner.WithCidrList([]string{ "162.159.192.0/24", "162.159.193.0/24",