Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions pkg/smokescreen/smokescreen.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const (
LogMitmReqMethod = "mitm_req_method"
LogMitmReqHeaders = "mitm_req_headers"
)

type ipType int

type ACLDecision struct {
Expand Down Expand Up @@ -182,13 +183,23 @@ const roleHeader = "X-Smokescreen-Role"
const traceHeader = "X-Smokescreen-Trace-ID"

func addrIsInRuleRange(ranges []RuleRange, addr *net.TCPAddr) bool {
addrIsV4 := addr.IP.To4() != nil
for _, rng := range ranges {
// If the range specifies a port and the port doesn't match,
// then this range doesn't match
if rng.Port != 0 && addr.Port != rng.Port {
continue
}

// Match only within the same address family. The range's declared
// family is determined from its parsed mask length rather than To4(),
// because IPv4-mapped IPv6 ranges (e.g. "::ffff:0:0/96") would otherwise
// collapse to "0.0.0.0/0" via net.IPNet.Contains -> To4() and match every
// IPv4 address. IPv4-mapped destinations are converted to IPv4 elsewhere,
// so an IPv6-written range should never match an IPv4 address.
rangeIsV4 := len(rng.Net.Mask) == net.IPv4len
if rangeIsV4 != addrIsV4 {
continue
}
if rng.Net.Contains(addr.IP) {
return true
}
Expand Down Expand Up @@ -655,7 +666,7 @@ func rejectResponse(pctx *goproxy.ProxyCtx, err error) *http.Response {
resp.ProtoMajor = pctx.Req.ProtoMajor
resp.ProtoMinor = pctx.Req.ProtoMinor
resp.Header.Set(errorHeader, msg)

// Add Retry-After header for tunnel limit errors
if _, ok := err.(tunnelLimitError); ok {
resp.Header.Set("Retry-After", "1")
Expand Down
43 changes: 43 additions & 0 deletions pkg/smokescreen/smokescreen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2200,3 +2200,46 @@ func TestMaxConcurrentConnectTunnels(t *testing.T) {
}
})
}

func TestAddrIsInRuleRange(t *testing.T) {
mustParseCIDR := func(s string) net.IPNet {
_, n, err := net.ParseCIDR(s)
require.NoError(t, err)
return *n
}

tests := []struct {
name string
ranges []RuleRange
addr *net.TCPAddr
expected bool
}{
{
name: "IPv4 within range",
ranges: []RuleRange{{Net: mustParseCIDR("10.0.0.0/8")}},
addr: &net.TCPAddr{IP: net.ParseIP("10.1.2.3"), Port: 80},
expected: true,
},
{
name: "non-matching port",
ranges: []RuleRange{{Net: mustParseCIDR("10.0.0.0/8"), Port: 443}},
addr: &net.TCPAddr{IP: net.ParseIP("10.1.2.3"), Port: 80},
expected: false,
},
{
// The address-family guard: an IPv4-mapped IPv6 range collapses to
// "0.0.0.0/0" via net.IPNet.Contains, so without the guard this would
// wrongly match every IPv4 address.
name: "IPv4-mapped IPv6 range does not match IPv4 address",
ranges: []RuleRange{{Net: mustParseCIDR("::ffff:0:0/96")}},
addr: &net.TCPAddr{IP: net.ParseIP("8.8.8.8"), Port: 80},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, addrIsInRuleRange(tt.ranges, tt.addr))
})
}
}