package main import ( "context" "errors" "net/http" "net/http/httptest" "net/netip" "testing" "github.com/go-gost/plugin/bypass/proto" ) func TestBypass(t *testing.T) { for _, tc := range []struct { name string addr string ips []netip.Addr err error deny bool }{ {name: "listed IPv4", addr: "203.0.113.1:443"}, {name: "foreign IPv4", addr: "198.51.100.1:443", deny: true}, {name: "listed IPv6", addr: "[2001:db8::1]:443"}, {name: "bare IPv6", addr: "2001:db8::1"}, {name: "foreign IPv6", addr: "[2001:db9::1]:443", deny: true}, {name: "mapped IPv4", addr: "[::ffff:203.0.113.1]:443"}, {name: "listed domain", addr: "allowed.test:443", ips: []netip.Addr{netip.MustParseAddr("203.0.113.2")}}, {name: "mixed answers", addr: "mixed.test:443", ips: []netip.Addr{netip.MustParseAddr("203.0.113.2"), netip.MustParseAddr("198.51.100.2")}, deny: true}, {name: "no addresses", addr: "empty.test:443", deny: true}, {name: "DNS failure", addr: "error.test:443", err: errors.New("DNS unavailable"), deny: true}, } { t.Run(tc.name, func(t *testing.T) { s := &server{ list: []netip.Prefix{netip.MustParsePrefix("203.0.113.0/24"), netip.MustParsePrefix("2001:db8::/32")}, lookup: func(ctx context.Context, _, _ string) ([]netip.Addr, error) { if _, ok := ctx.Deadline(); !ok { t.Error("DNS lookup has no deadline") } return tc.ips, tc.err }, } reply, err := s.Bypass(context.Background(), &proto.BypassRequest{Addr: tc.addr}) if err != nil || reply.Ok != tc.deny { t.Fatalf("Bypass = %v, %v; want deny=%v", reply, err, tc.deny) } }) } } func TestRefresh(t *testing.T) { status, body := 200, "# list\n203.0.113.7/24 # normalize\n2001:db8::/32\n" upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(status) _, _ = w.Write([]byte(body)) })) defer upstream.Close() s := &server{} if err := s.refresh(upstream.URL); err != nil { t.Fatal(err) } for _, tc := range []struct { name string status int body string }{ {"HTTP failure", 503, "unavailable"}, {"empty list", 200, "# empty\n"}, {"malformed list", 200, "198.51.100.0/24\nerror"}, } { t.Run(tc.name, func(t *testing.T) { status, body = tc.status, tc.body if err := s.refresh(upstream.URL); err == nil { t.Fatal("accepted invalid list") } reply, _ := s.Bypass(context.Background(), &proto.BypassRequest{Addr: "203.0.113.1:443"}) if reply.Ok || len(s.list) != 2 { t.Fatal("failed refresh discarded previous rules") } }) } status, body = 200, "198.51.100.0/24\n" if err := s.refresh(upstream.URL); err != nil { t.Fatal(err) } old, _ := s.Bypass(context.Background(), &proto.BypassRequest{Addr: "203.0.113.1:443"}) updated, _ := s.Bypass(context.Background(), &proto.BypassRequest{Addr: "198.51.100.1:443"}) if !old.Ok || updated.Ok { t.Fatal("refresh did not replace rules") } }