From 106bb527df272efadc26bc0fb4bb832592168260 Mon Sep 17 00:00:00 2001 From: egorgasay Date: Fri, 26 May 2023 16:26:48 +0300 Subject: [PATCH] :sparkles: NewDirectorSetHeadersByIP now forms an array with networks based on inclusion (outer networks before inner) and produces the same results on multiple runs. --- cmd/static/default-config.toml | 8 +-- internal/proxy/config.go | 2 +- internal/proxy/config_test.go | 16 +++--- internal/proxy/directors.go | 85 +++++++++++++++++++++++++++++++- internal/proxy/directors_test.go | 80 ++++++++++++++++++++++++++++++ 5 files changed, 177 insertions(+), 14 deletions(-) diff --git a/cmd/static/default-config.toml b/cmd/static/default-config.toml index 8ff327d..97ead51 100644 --- a/cmd/static/default-config.toml +++ b/cmd/static/default-config.toml @@ -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 diff --git a/internal/proxy/config.go b/internal/proxy/config.go index 1a55a85..4e27cfe 100644 --- a/internal/proxy/config.go +++ b/internal/proxy/config.go @@ -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", diff --git a/internal/proxy/config_test.go b/internal/proxy/config_test.go index 0ceb9a8..206058e 100644 --- a/internal/proxy/config_test.go +++ b/internal/proxy/config_test.go @@ -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", }, @@ -377,7 +377,7 @@ 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: "*/*"}, @@ -385,7 +385,7 @@ func TestConfig_getHeadersByIPDirector(t *testing.T) { }, }, 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"}, @@ -393,7 +393,7 @@ func TestConfig_getHeadersByIPDirector(t *testing.T) { }, }, 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"}, @@ -401,7 +401,7 @@ func TestConfig_getHeadersByIPDirector(t *testing.T) { }, }, 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"}, diff --git a/internal/proxy/directors.go b/internal/proxy/directors.go index a0ffd51..5ba03b6 100644 --- a/internal/proxy/directors.go +++ b/internal/proxy/directors.go @@ -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 { diff --git a/internal/proxy/directors_test.go b/internal/proxy/directors_test.go index 886635e..2538e54 100644 --- a/internal/proxy/directors_test.go +++ b/internal/proxy/directors_test.go @@ -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) + } + }) + } +}