// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package proxy import ( "context" "errors" "fmt" "net" "slices" "testing" ) type recordingProxy struct { addrs []string } func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) { r.addrs = append(r.addrs, addr) return nil, errors.New("recordingProxy") } func TestPerHost(t *testing.T) { for _, test := range []struct { config string // passed to PerHost.AddFromString nomatch []string // addrs using the default dialer match []string // addrs using the bypass dialer }{{ config: "localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16", nomatch: []string{ "example.com:123", "1.2.3.4:123", "[1001::]:123", }, match: []string{ "localhost:123", "zone:123", "foo.zone:123", "127.0.0.1:123", "10.1.2.3:123", "[1000::]:123", "[1000::%25.example.com]:123", }, }, { config: "localhost", nomatch: []string{ "127.0.0.1:80", }, match: []string{ "localhost:80", }, }, { config: "*.zone", nomatch: []string{ "foo.com:80", }, match: []string{ "foo.zone:80", "foo.bar.zone:80", }, }, { config: "1.2.3.4", nomatch: []string{ "127.0.0.1:80", "11.2.3.4:80", }, match: []string{ "1.2.3.4:80", }, }, { config: "10.0.0.0/24", nomatch: []string{ "10.0.1.1:80", }, match: []string{ "10.0.0.1:80", "10.0.0.255:80", }, }, { config: "fe80::/10", nomatch: []string{ "[fec0::1]:80", "[fec0::1%en0]:80", }, match: []string{ "[fe80::1]:80", "[fe80::1%en0]:80", }, }, { // We don't allow zone IDs in network prefixes, // so this config matches nothing. config: "fe80::%en0/10", nomatch: []string{ "[fec0::1]:80", "[fec0::1%en0]:80", "[fe80::1]:80", "[fe80::1%en0]:80", "[fe80::1%en1]:80", }, }} { for _, addr := range test.match { testPerHost(t, test.config, addr, true) } for _, addr := range test.nomatch { testPerHost(t, test.config, addr, false) } } } func testPerHost(t *testing.T, config, addr string, wantMatch bool) { name := fmt.Sprintf("config %q, dial %q", config, addr) var def, bypass recordingProxy perHost := NewPerHost(&def, &bypass) perHost.AddFromString(config) perHost.Dial("tcp", addr) // Dial and DialContext should have the same results. var defc, bypassc recordingProxy perHostc := NewPerHost(&defc, &bypassc) perHostc.AddFromString(config) perHostc.DialContext(context.Background(), "tcp", addr) if !slices.Equal(def.addrs, defc.addrs) { t.Errorf("%v: Dial default=%v, bypass=%v; DialContext default=%v, bypass=%v", name, def.addrs, bypass.addrs, defc.addrs, bypass.addrs) return } if got, want := slices.Concat(def.addrs, bypass.addrs), []string{addr}; !slices.Equal(got, want) { t.Errorf("%v: dialed %q, want %q", name, got, want) return } gotMatch := len(bypass.addrs) > 0 if gotMatch != wantMatch { t.Errorf("%v: matched=%v, want %v", name, gotMatch, wantMatch) return } }