Skip to content

Commit

Permalink
✨ NewDirectorSetHeadersByIP now forms an array with networks based on…
Browse files Browse the repository at this point in the history
… inclusion (outer networks before inner) and produces the same results on multiple runs.
  • Loading branch information
egorgasay committed May 26, 2023
1 parent bd5b0d7 commit 106bb52
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 14 deletions.
8 changes: 4 additions & 4 deletions cmd/static/default-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,17 @@ TargetMap = []
# ["IP:{{SOURCE_IP}}", "Proxy:lets-proxy", "Protocol:{{HTTP_PROTO}}" ]
Headers = [ "X-Forwarded-Proto:{{HTTP_PROTO}}", "X-Forwarded-For:{{SOURCE_IP}}" ]

# A map with an IP key/mask and a value with an array of strings separated by a colon Header:Header Value

# A map with an IP key/mask and a value with an array of strings separated by a colon Header:Value
# to add to a request with matching ip address for backend.
# Example:
# [Proxy.HeadersByIP]
#"192.168.0.0/24" = ["IPLocal:Test"]
#"192.168.2.0/24" = [
#"fe80:0000:0000:0000::/64" = [
# "IPLocal:Test2",
# "Local:False"
# ]
[Proxy.HeadersByIP]
"127.0.0.0/24" = []
# [Proxy.HeadersByIP]

# Use https requests to backend instead of http
HTTPSBackend = false
Expand Down
2 changes: 1 addition & 1 deletion internal/proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (c *Config) getSchemaDirector(ctx context.Context) (Director, error) {
// can return nil,nil
// example:
//
// [Proxy.HTTPHeaders]
// [Proxy.HeadersByIP]
// "192.168.1.0/24" = [
//
// "User-Agent:PostmanRuntime/7.29.2",
Expand Down
16 changes: 8 additions & 8 deletions internal/proxy/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,23 +348,23 @@ func TestConfig_getHeadersByIPDirector(t *testing.T) {
name: "5Networks",
c: Config{
HeadersByIP: map[string][]string{
"192.168.1.0/24": {
"10.0.0.0/8": {
"User-Agent:PostmanRuntime/7.29.2",
"Accept:*/*",
"Accept-Encoding:gzip, deflate, br",
},
"172.16.33.0/24": {
"10.0.0.0/24": {
"Connection:Keep-Alive",
"Upgrade-Insecure-Requests:1",
"Cache-Control:no-cache",
},
"172.16.99.0/24": {
"10.0.1.0/24": {
"Origin:https://example.com",
"Content-Type:application/json",
"Content-Length:123",
},

"192.168.32.0/24": {
"10.2.0.0/24": {
"Accept-Encoding:gzip, deflate, br",
"Accept-Language:en-US,en;q=0.9",
},
Expand All @@ -377,31 +377,31 @@ func TestConfig_getHeadersByIPDirector(t *testing.T) {
},
want: DirectorSetHeadersByIP{
NetHeaders{
IPNet: net.IPNet{IP: net.ParseIP("192.168.1.0"), Mask: net.CIDRMask(24, 32)},
IPNet: net.IPNet{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(8, 32)},
Headers: HTTPHeaders{
{Name: "User-Agent", Value: "PostmanRuntime/7.29.2"},
{Name: "Accept", Value: "*/*"},
{Name: "Accept-Encoding", Value: "gzip, deflate, br"},
},
},
NetHeaders{
IPNet: net.IPNet{IP: net.ParseIP("172.16.33.0"), Mask: net.CIDRMask(24, 32)},
IPNet: net.IPNet{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(24, 32)},
Headers: HTTPHeaders{
{Name: "Connection", Value: "Keep-Alive"},
{Name: "Upgrade-Insecure-Requests", Value: "1"},
{Name: "Cache-Control", Value: "no-cache"},
},
},
NetHeaders{
IPNet: net.IPNet{IP: net.ParseIP("172.16.99.0"), Mask: net.CIDRMask(24, 32)},
IPNet: net.IPNet{IP: net.ParseIP("10.0.1.0"), Mask: net.CIDRMask(24, 32)},
Headers: HTTPHeaders{
{Name: "Origin", Value: "https://example.com"},
{Name: "Content-Type", Value: "application/json"},
{Name: "Content-Length", Value: "123"},
},
},
NetHeaders{
IPNet: net.IPNet{IP: net.ParseIP("192.168.32.0"), Mask: net.CIDRMask(24, 32)},
IPNet: net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(24, 32)},
Headers: HTTPHeaders{
{Name: "Accept-Encoding", Value: "gzip, deflate, br"},
{Name: "Accept-Language", Value: "en-US,en;q=0.9"},
Expand Down
85 changes: 84 additions & 1 deletion internal/proxy/directors.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,90 @@ func NewDirectorSetHeadersByIP(m map[string]HTTPHeaders) (DirectorSetHeadersByIP
Headers: v,
})
}
return res, nil

return sortByIPNet(res), nil
}

// sortByIPNet sorts by CIDR using quicksort algorithm.
func sortByIPNet(d DirectorSetHeadersByIP) DirectorSetHeadersByIP {
ipv4 := make(DirectorSetHeadersByIP, 0, len(d))
ipv6 := make(DirectorSetHeadersByIP, 0, len(d))
for _, item := range d {
if item.IPNet.IP.To4() != nil {
ipv4 = append(ipv4, item)
} else {
ipv6 = append(ipv6, item)
}
}

ipv4 = quickSortByIPNet(ipv4)
ipv6 = quickSortByIPNet(ipv6)

return append(ipv4, ipv6...)
}

// quickSortByIPNet sorts by CIDR using quicksort algorithm.
// The result is sorted by IPNet.
// example:
//
// IN -> DirectorSetHeadersByIP{
// {IPNet: net.IPNet{IP: net.ParseIP("192.168.88.0"), Mask: net.CIDRMask(24, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("192.0.0.0"), Mask: net.CIDRMask(8, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(16, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("192.168.99.0"), Mask: net.CIDRMask(24, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("172.0.0.0"), Mask: net.CIDRMask(8, 32)}},
// },
//
// OUT <- DirectorSetHeadersByIP{
// {IPNet: net.IPNet{IP: net.ParseIP("192.0.0.0"), Mask: net.CIDRMask(8, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("192.168.88.0"), Mask: net.CIDRMask(24, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("192.168.99.0"), Mask: net.CIDRMask(24, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("172.0.0.0"), Mask: net.CIDRMask(8, 32)}},
// {IPNet: net.IPNet{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(16, 32)}},
// },
func quickSortByIPNet(d DirectorSetHeadersByIP) DirectorSetHeadersByIP {
if len(d) <= 1 {
return d
}

mid := len(d) / 2
left := d[:mid]
right := d[mid:]

left = quickSortByIPNet(left)
right = quickSortByIPNet(right)

return mergeByIPNet(left, right)
}

// mergeByIPNet merges two sorted arrays with CIDRs.
// The result is sorted by IPNet.
func mergeByIPNet(left, right DirectorSetHeadersByIP) DirectorSetHeadersByIP {
res := make(DirectorSetHeadersByIP, 0, len(left)+len(right))
for len(left) > 0 || len(right) > 0 {
if len(left) > 0 && len(right) > 0 {
if left[0].IPNet.Contains(right[0].IPNet.IP) {
res = append(res, left[0])
left = left[1:]
} else if right[0].IPNet.Contains(left[0].IPNet.IP) {
res = append(res, right[0])
right = right[1:]
} else {
res = append(res, left[0], right[0])
left = left[1:]
right = right[1:]
}
} else if len(left) > 0 {
res = append(res, left[0])
left = left[1:]
} else if len(right) > 0 {
res = append(res, right[0])
right = right[1:]
}
}
return res
}

func (h DirectorSetHeadersByIP) Director(request *http.Request) error {
Expand Down
80 changes: 80 additions & 0 deletions internal/proxy/directors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,83 @@ func TestNewDirectorSetHeadersByIP(t *testing.T) {
})
}
}

func Test_sortByIPNet(t *testing.T) {
_, flush := th.TestContext(t)
defer flush()
td := testdeep.NewT(t)

type args struct {
d DirectorSetHeadersByIP
}
tests := []struct {
name string
args args
want DirectorSetHeadersByIP
}{
{
name: "IPv4Only",
args: args{
d: DirectorSetHeadersByIP{
{IPNet: net.IPNet{IP: net.ParseIP("192.168.88.0"), Mask: net.CIDRMask(24, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.0.0.0"), Mask: net.CIDRMask(8, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(16, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.99.0"), Mask: net.CIDRMask(24, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("172.0.0.0"), Mask: net.CIDRMask(8, 32)}},
},
},
want: DirectorSetHeadersByIP{
{IPNet: net.IPNet{IP: net.ParseIP("192.0.0.0"), Mask: net.CIDRMask(8, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.88.0"), Mask: net.CIDRMask(24, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.99.0"), Mask: net.CIDRMask(24, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("172.0.0.0"), Mask: net.CIDRMask(8, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(16, 32)}},
},
},
{
name: "IPv6Only",
args: args{
d: DirectorSetHeadersByIP{
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234:5678::"), Mask: net.CIDRMask(64, 128)}},
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234::"), Mask: net.CIDRMask(48, 128)}},
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234:5678:abcd::"), Mask: net.CIDRMask(80, 128)}},
},
},
want: DirectorSetHeadersByIP{
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234::"), Mask: net.CIDRMask(48, 128)}},
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234:5678::"), Mask: net.CIDRMask(64, 128)}},
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234:5678:abcd::"), Mask: net.CIDRMask(80, 128)}},
},
},

{
name: "IPv6AndIPv4",
args: args{
d: DirectorSetHeadersByIP{
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234:5678:abcd::"), Mask: net.CIDRMask(80, 128)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.88.0"), Mask: net.CIDRMask(24, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.0.0.0"), Mask: net.CIDRMask(8, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234:5678::"), Mask: net.CIDRMask(64, 128)}},
},
},
want: DirectorSetHeadersByIP{
{IPNet: net.IPNet{IP: net.ParseIP("192.0.0.0"), Mask: net.CIDRMask(8, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("192.168.88.0"), Mask: net.CIDRMask(24, 32)}},
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234:5678::"), Mask: net.CIDRMask(64, 128)}},
{IPNet: net.IPNet{IP: net.ParseIP("2001:db8:1234:5678:abcd::"), Mask: net.CIDRMask(80, 128)}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := sortByIPNet(tt.args.d)
if !td.CmpDeeply(got, tt.want) {
t.Errorf("sortByIPNet() = %v, want %v", tt.args.d, tt.want)
}
})
}
}

0 comments on commit 106bb52

Please sign in to comment.