Skip to content

Commit f6ac108

Browse files
committed
refractored stargate into a library
1 parent 716e8a9 commit f6ac108

File tree

15 files changed

+130
-38
lines changed

15 files changed

+130
-38
lines changed

.cspell.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,21 @@
55
"dictionaries": [],
66
"words": [
77
"bijective",
8+
"BINDANY",
89
"buildx",
910
"congruential",
11+
"errgroup",
12+
"Freebind",
1013
"goarch",
1114
"godoc",
1215
"golangci",
16+
"GOPROXY",
1317
"goreleaser",
1418
"ldflags",
1519
"Mersenne",
20+
"netip",
1621
"nonlocal",
22+
"stargate",
1723
"trimpath"
1824
],
1925
"ignoreWords": [],

.vscode/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

.vscode/settings.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ SOURCES := $(shell find . -type f -name "*.go")
99
.PHONY: all fmt clean docker lint
1010

1111
stargate: ${SOURCES} go.mod go.sum
12-
CGO_ENABLED=0 go build -ldflags "-w -s -X main.version=${VERSION}" -trimpath -o $@
12+
CGO_ENABLED=0 go build -ldflags "-w -s -X main.version=${VERSION}" -trimpath -o $@ cmd/*.go
1313

1414
clean:
1515
rm stargate

README.md

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Stargate
22

3-
Stargate is a TCP SOCKS5 proxy server that can egress traffic from multiple IP addresses within a subnet. It randomly distributes connections across different IP addresses to help avoid rate-limiting and provide load balancing across your available IP range.
3+
Stargate is both a Go library and a TCP SOCKS5 proxy server that can egress traffic from multiple IP addresses within a subnet. It randomly distributes connections across different IP addresses to help avoid rate-limiting and provide load balancing across your available IP range.
44

55
This requires the host running stargate to have the subnet routed directly to it.
66

7-
If you have an IPv6 subnet, stargate can allow you to make full use of it by any program that can speak SOCKS.
7+
If you have an IPv6 subnet, stargate can allow you to make full use of it by any program that can speak SOCKS, or you can use the library directly in your Go applications.
88

99
## Usage
1010

@@ -155,3 +155,73 @@ This will produce a statically linked binary that's ready to use.
155155
### Platforms
156156

157157
Stargate makes use of freebind, which allowed for creating a socket with a source IP address on an interface that does not have that IP directly bound to it. This is only available on Linux and FreeBSD. Stargate can work on other platforms, but it will require every address to be previously bound to the interface before running.
158+
159+
## Go Library
160+
161+
You can use Stargate as a Go library in your applications to make HTTP requests or other network connections using random source IP addresses:
162+
163+
```go
164+
package main
165+
166+
import (
167+
"context"
168+
"fmt"
169+
"net/http"
170+
"net/netip"
171+
"time"
172+
173+
"github.com/lanrat/stargate"
174+
)
175+
176+
func main() {
177+
// Parse your CIDR range
178+
prefix, err := netip.ParsePrefix("192.0.2.0/24")
179+
if err != nil {
180+
panic(err)
181+
}
182+
183+
// Create a RandomIPDialer for the subnet
184+
dialer, err := stargate.NewRandomIPIterator(prefix, 32)
185+
if err != nil {
186+
panic(err)
187+
}
188+
189+
// Create an HTTP client that uses random source IPs
190+
client := &http.Client{
191+
Transport: &http.Transport{
192+
DialContext: dialer.Dial,
193+
},
194+
Timeout: 10 * time.Second,
195+
}
196+
197+
// Make requests - each will use a different random IP
198+
for i := 0; i < 5; i++ {
199+
resp, err := client.Get("https://httpbin.org/ip")
200+
if err != nil {
201+
fmt.Printf("Request %d failed: %v\n", i+1, err)
202+
continue
203+
}
204+
resp.Body.Close()
205+
fmt.Printf("Request %d: Status %d\n", i+1, resp.StatusCode)
206+
}
207+
}
208+
```
209+
210+
You can also get the next random IP and DialFunc separately:
211+
212+
```go
213+
// Get next random IP and dialer function
214+
ip, dialFunc, err := dialer.NextDial()
215+
if err != nil {
216+
panic(err)
217+
}
218+
219+
fmt.Printf("Next egress IP will be: %s\n", ip)
220+
221+
// Use the dial function directly
222+
conn, err := dialFunc(context.Background(), "tcp", "example.com:80")
223+
if err != nil {
224+
panic(err)
225+
}
226+
defer conn.Close()
227+
```

addresses.go renamed to broadcast_addresses.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package stargate
22

33
import (
44
"fmt"
@@ -7,21 +7,23 @@ import (
77
)
88

99
// broadcastAddrs is a global map that tracks IP addresses identified as broadcast addresses.
10-
// It is populated by checkHostConflicts and used to prevent binding to these addresses.
10+
// It is populated by CheckHostConflicts and used to prevent binding to these addresses.
1111
var broadcastAddrs = make(map[string]bool)
1212

13-
// checkHostConflicts detects if any of the addresses we are going to use are broadcast addresses.
13+
// CheckHostConflicts detects if any of the addresses we are going to use are broadcast addresses.
1414
// It populates the global broadcastAddrs map by examining all system network interfaces.
15-
func checkHostConflicts(prefix *netip.Prefix) error {
15+
// It returns a list of all conflicting IPs
16+
func CheckHostConflicts(prefix *netip.Prefix) ([]net.IP, error) {
1617
interfaces, err := net.Interfaces()
1718
if err != nil {
18-
return err
19+
return nil, err
1920
}
2021

22+
conflictIPs := make([]net.IP, 0)
2123
for _, i := range interfaces {
2224
addrs, err := i.Addrs()
2325
if err != nil {
24-
return err
26+
return nil, err
2527
}
2628
for _, a := range addrs {
2729
ipnet, ok := a.(*net.IPNet)
@@ -34,20 +36,21 @@ func checkHostConflicts(prefix *netip.Prefix) error {
3436
}
3537
brdIP, err := getBroadcastAddressFromAddr(ipnet)
3638
if err != nil {
37-
return err
39+
return nil, err
3840
}
3941
brdAddr, ok := netip.AddrFromSlice(brdIP)
4042
if !ok {
41-
return fmt.Errorf("unable to parse IP to addr: %+v", brdAddr)
43+
return nil, fmt.Errorf("unable to parse IP to addr: %+v", brdAddr)
4244
}
4345
if prefix.Contains(brdAddr) {
4446
broadcastAddrs[brdAddr.String()] = true
45-
l.Printf("WARNING: interface %s broadcast address is within provided prefix %s", i.Name, brdIP)
47+
v("WARNING: interface %s broadcast address is within provided prefix %s", i.Name, brdIP)
48+
conflictIPs = append(conflictIPs, brdIP)
4649
}
4750
}
4851
}
4952

50-
return nil
53+
return conflictIPs, nil
5154
}
5255

5356
// getBroadcastAddressFromAddr calculates the broadcast address from a net.IPNet.

main.go renamed to cmd/main.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313

1414
"github.com/haxii/socks5"
15+
"github.com/lanrat/stargate"
1516
)
1617

1718
// Command-line flags
@@ -83,18 +84,25 @@ func main() {
8384
// more could be supported by switching from uint64 to big.Int types.
8485
// for even with IPv6, using more than 2^64 networks is incredibly unlikely
8586
// if someone dares to do this, error
86-
totalNetworks := *subnetBits - uint(cidrBits)
87-
if totalNetworks > 64 {
88-
log.Fatalf("subnet host range too large. Can't run on over 2^64 networks, got 2^%d", totalNetworks)
87+
totalNetworksBits := *subnetBits - uint(cidrBits)
88+
if totalNetworksBits > 64 {
89+
l.Fatalf("subnet host range too large. Can't run on over 2^64 networks, got 2^%d", totalNetworksBits)
8990
}
9091

92+
// set stargate Logger
93+
stargate.Logger = v
94+
9195
// check for IP conflicts
92-
err = checkHostConflicts(&parsedNetwork)
96+
conflicts, err := stargate.CheckHostConflicts(&parsedNetwork)
9397
if err != nil {
9498
l.Fatal(err)
9599
}
100+
for _, ip := range conflicts {
101+
l.Printf("Warning: possible IP conflict on %s", ip)
102+
}
96103

97104
hostsPerNetwork := 1 << (maxNetworkBits - *subnetBits)
105+
totalNetworks := 1 << totalNetworksBits
98106
l.Printf("Running with subnet size /%d and /%d prefix resulting in %d egress networks and %d options per network", *subnetBits, cidrBits, totalNetworks, hostsPerNetwork)
99107

100108
// test mode
@@ -128,7 +136,7 @@ func main() {
128136
}
129137

130138
// v logs a message if verbose logging is enabled.
131-
func v(format string, a ...interface{}) {
139+
func v(format string, a ...any) {
132140
if *verbose {
133141
l.Printf(format, a...)
134142
}
File renamed without changes.

socks.go renamed to cmd/socks.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/netip"
55

66
"github.com/haxii/socks5"
7+
"github.com/lanrat/stargate"
78
)
89

910
// runRandomSubnetProxy starts a SOCKS5 proxy server listening on listenAddr that distributes
@@ -12,7 +13,7 @@ import (
1213
// This is memory efficient for large IPv6 ranges as it doesn't pre-generate all addresses.
1314
// The function cycles through all available subnets before repeating.
1415
func runRandomSubnetProxy(listenAddr string, parsedNetwork netip.Prefix, cidrSize uint) error {
15-
ipItr, err := NewRandomIPIterator(parsedNetwork, cidrSize)
16+
ipItr, err := stargate.NewRandomIPIterator(parsedNetwork, cidrSize)
1617
if err != nil {
1718
return err
1819
}

test.go renamed to cmd/test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"sync/atomic"
1313
"time"
1414

15+
"github.com/lanrat/stargate"
1516
"golang.org/x/sync/errgroup"
1617
)
1718

@@ -27,15 +28,15 @@ const (
2728
// testDial represents a test configuration pairing an IP address with its corresponding dialer function.
2829
type testDial struct {
2930
ip net.IP
30-
dial DialFunc
31+
dial stargate.DialFunc
3132
}
3233

3334
// test validates that all IP addresses in the given CIDR range can successfully
3435
// make HTTP requests and receive the expected source IP in the response.
3536
// It uses cloudflare.com/cdn-cgi/trace to verify the egress IP address.
3637
func test(ctx context.Context, parsedNetwork netip.Prefix, cidrSize uint) error {
3738
// Create iterator for all host indices
38-
ipItr, err := NewRandomIPIterator(parsedNetwork, cidrSize)
39+
ipItr, err := stargate.NewRandomIPIterator(parsedNetwork, cidrSize)
3940
if err != nil {
4041
return err
4142
}
@@ -144,7 +145,7 @@ func test(ctx context.Context, parsedNetwork netip.Prefix, cidrSize uint) error
144145

145146
// testWithDialer performs an HTTP request using the provided dialer and verifies
146147
// that the egress IP matches the expected IP address by querying cloudflare.com/cdn-cgi/trace.
147-
func testWithDialer(ctx context.Context, dial DialFunc, expectedIP net.IP) error {
148+
func testWithDialer(ctx context.Context, dial stargate.DialFunc, expectedIP net.IP) error {
148149
ctx, cancel := context.WithTimeout(ctx, testTimeout)
149150
defer cancel()
150151

@@ -161,7 +162,7 @@ func testWithDialer(ctx context.Context, dial DialFunc, expectedIP net.IP) error
161162
resp, err := client.Do(req)
162163
if err != nil {
163164
// If bindError, then unwrap
164-
var bindErr *IPBindError
165+
var bindErr *stargate.IPBindError
165166
if errors.As(err, &bindErr) {
166167
return bindErr
167168
}

0 commit comments

Comments
 (0)