1
0

json_routing_test.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package sub
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "net/http"
  6. "strings"
  7. "testing"
  8. )
  9. func mustMarshal(t *testing.T, v any) string {
  10. t.Helper()
  11. data, err := json.Marshal(v)
  12. if err != nil {
  13. t.Fatalf("marshal: %v", err)
  14. }
  15. return string(data)
  16. }
  17. func b64Std(s string) string { return base64.StdEncoding.EncodeToString([]byte(s)) }
  18. func b64URL(s string) string { return base64.RawURLEncoding.EncodeToString([]byte(s)) }
  19. func fullRoutingPayload() map[string]any {
  20. return map[string]any{
  21. "Name": "RoscomVPN",
  22. "DomainStrategy": "IPIfNonMatch",
  23. "RemoteDNSDomain": "https://8.8.8.8/dns-query",
  24. "RemoteDNSIP": "8.8.8.8",
  25. "DomesticDNSDomain": "https://77.88.8.8/dns-query",
  26. "DomesticDNSIP": "77.88.8.8",
  27. "DnsHosts": map[string]any{"lknpd.nalog.ru": "213.24.64.181"},
  28. "RouteOrder": "block-proxy-direct",
  29. "DirectSites": []any{"geosite:category-ru", "geosite:private"},
  30. "DirectIp": []any{"geoip:private"},
  31. "ProxySites": []any{"geosite:youtube"},
  32. "ProxyIp": []any{},
  33. "BlockSites": []any{"geosite:category-ads"},
  34. "BlockIp": []any{},
  35. }
  36. }
  37. func TestParseJsonRoutingSpecMapsAllFields(t *testing.T) {
  38. spec, remote, err := parseJsonRoutingSpec(mustMarshal(t, fullRoutingPayload()))
  39. if err != nil || remote {
  40. t.Fatalf("parse: err=%v remote=%v", err, remote)
  41. }
  42. want := jsonRoutingSpec{
  43. DomainStrategy: "IPIfNonMatch",
  44. RemoteDNSDomain: "https://8.8.8.8/dns-query",
  45. RemoteDNSIP: "8.8.8.8",
  46. DomesticDNSDomain: "https://77.88.8.8/dns-query",
  47. DomesticDNSIP: "77.88.8.8",
  48. DnsHosts: map[string]string{"lknpd.nalog.ru": "213.24.64.181"},
  49. RouteOrder: []string{"block", "proxy", "direct"},
  50. DirectSites: []string{"geosite:category-ru", "geosite:private"},
  51. DirectIp: []string{"geoip:private"},
  52. ProxySites: []string{"geosite:youtube"},
  53. BlockSites: []string{"geosite:category-ads"},
  54. }
  55. if spec.DomainStrategy != want.DomainStrategy || spec.RemoteDNSIP != want.RemoteDNSIP ||
  56. spec.DomesticDNSDomain != want.DomesticDNSDomain || len(spec.DnsHosts) != 1 || spec.DnsHosts["lknpd.nalog.ru"] != "213.24.64.181" ||
  57. strings.Join(spec.RouteOrder, ",") != strings.Join(want.RouteOrder, ",") ||
  58. strings.Join(spec.DirectSites, ",") != strings.Join(want.DirectSites, ",") ||
  59. strings.Join(spec.DirectIp, ",") != strings.Join(want.DirectIp, ",") ||
  60. strings.Join(spec.ProxySites, ",") != strings.Join(want.ProxySites, ",") ||
  61. strings.Join(spec.BlockSites, ",") != strings.Join(want.BlockSites, ",") {
  62. t.Fatalf("spec = %+v\nwant %+v", spec, want)
  63. }
  64. }
  65. func TestParseJsonRoutingSpecPartialPayload(t *testing.T) {
  66. spec, _, err := parseJsonRoutingSpec(`{"DirectSites":["geosite:private"],"DomainStrategy":"AsIs"}`)
  67. if err != nil {
  68. t.Fatalf("parse: %v", err)
  69. }
  70. if spec.DomainStrategy != "AsIs" || len(spec.DirectSites) != 1 || spec.DirectSites[0] != "geosite:private" {
  71. t.Fatalf("spec = %+v", spec)
  72. }
  73. if len(spec.RouteOrder) != 0 || len(spec.DnsHosts) != 0 || spec.RemoteDNSIP != "" {
  74. t.Fatalf("unset fields must stay zero: %+v", spec)
  75. }
  76. if spec.empty() {
  77. t.Fatalf("empty() must report false when any field is set: %+v", spec)
  78. }
  79. }
  80. func TestParseJsonRoutingSpecRouteOrderUnknownSegments(t *testing.T) {
  81. spec, _, err := parseJsonRoutingSpec(`{"RouteOrder":"block-foo-direct"}`)
  82. if err != nil {
  83. t.Fatalf("parse: %v", err)
  84. }
  85. if strings.Join(spec.RouteOrder, ",") != "block,direct" {
  86. t.Fatalf("RouteOrder = %v", spec.RouteOrder)
  87. }
  88. }
  89. func TestParseJsonRoutingSpecDeeplinks(t *testing.T) {
  90. payload := mustMarshal(t, fullRoutingPayload())
  91. cases := []string{
  92. "happ://routing/onadd/" + b64Std(payload),
  93. "incy://routing/onadd/" + b64Std(payload),
  94. "happ://routing/onadd/" + b64URL(payload),
  95. }
  96. for _, raw := range cases {
  97. spec, _, err := parseJsonRoutingSpec(raw)
  98. if err != nil {
  99. t.Fatalf("parse %q: %v", raw[:32], err)
  100. }
  101. if spec.DomainStrategy != "IPIfNonMatch" || len(spec.DirectSites) != 2 || spec.RouteOrder[1] != "proxy" {
  102. t.Fatalf("spec from %q = %+v", raw[:32], spec)
  103. }
  104. }
  105. }
  106. func TestParseJsonRoutingSpecRejectsBadPayloads(t *testing.T) {
  107. cases := []string{
  108. "not json at all",
  109. "[1,2,3]",
  110. `{"DirectSites":"geosite:private"}`,
  111. `{"DirectSites":["a",1]}`,
  112. `{"DnsHosts":{"a":1}}`,
  113. `{"DomainStrategy":5}`,
  114. "happ://routing/onadd/!!!!not-base64!!!!",
  115. }
  116. for _, raw := range cases {
  117. if _, _, err := parseJsonRoutingSpec(raw); err == nil {
  118. t.Fatalf("payload %q was accepted", raw)
  119. }
  120. }
  121. }
  122. func TestParseJsonRoutingSpecEmpty(t *testing.T) {
  123. for _, raw := range []string{"", " ", "\n"} {
  124. spec, remote, err := parseJsonRoutingSpec(raw)
  125. if err != nil || remote || !spec.empty() {
  126. t.Fatalf("raw=%q spec=%+v remote=%v err=%v", raw, spec, remote, err)
  127. }
  128. }
  129. }
  130. func TestParseJsonRoutingSpecRemoteURL(t *testing.T) {
  131. oldResolver := routingSourceResolver
  132. t.Cleanup(func() { routingSourceResolver = oldResolver })
  133. routingSourceResolver = newRemoteRoutingResolver(remoteRoutingTestClient(func(*http.Request) (*http.Response, error) {
  134. return remoteRoutingResponse(200, mustMarshal(t, fullRoutingPayload())), nil
  135. }), false)
  136. const source = "https://example.com/DEFAULT.JSON"
  137. primeRemoteRouting(t, routingSourceResolver, remoteRoutingJson, source)
  138. spec, _, err := parseJsonRoutingSpec(source)
  139. if err != nil {
  140. t.Fatalf("parse: err=%v", err)
  141. }
  142. if spec.DomainStrategy != "IPIfNonMatch" || len(spec.BlockSites) != 1 {
  143. t.Fatalf("spec = %+v", spec)
  144. }
  145. }
  146. func TestParseJsonRoutingSpecRemoteUnavailable(t *testing.T) {
  147. oldResolver := routingSourceResolver
  148. t.Cleanup(func() { routingSourceResolver = oldResolver })
  149. routingSourceResolver = newRemoteRoutingResolver(remoteRoutingTestClient(func(*http.Request) (*http.Response, error) {
  150. return remoteRoutingResponse(200, "routing.help"), nil
  151. }), false)
  152. if _, _, err := parseJsonRoutingSpec("https://example.com/bad"); err == nil {
  153. t.Fatal("unavailable remote source must error")
  154. }
  155. }
  156. // normalizeHappRouting accepts happ://routing/add/ as a routing deeplink
  157. // (remote_routing.go), so the baked-JSON parser has to accept it too.
  158. func TestParseJsonRoutingSpecAcceptsAddDeeplink(t *testing.T) {
  159. payload := mustMarshal(t, fullRoutingPayload())
  160. for _, prefix := range []string{"happ://routing/onadd/", "happ://routing/add/", "incy://routing/onadd/"} {
  161. t.Run(prefix, func(t *testing.T) {
  162. if _, err := normalizeHappRouting([]byte(prefix + b64Std(payload))); err != nil &&
  163. !strings.HasPrefix(prefix, "incy://") {
  164. t.Fatalf("normalizeHappRouting rejects %s: %v", prefix, err)
  165. }
  166. spec, remote, err := parseJsonRoutingSpec(prefix + b64Std(payload))
  167. if err != nil || remote {
  168. t.Fatalf("parse %s: err=%v remote=%v", prefix, err, remote)
  169. }
  170. if spec.empty() {
  171. t.Fatalf("parse %s: spec is empty, routing would not be baked", prefix)
  172. }
  173. if spec.DomainStrategy != "IPIfNonMatch" {
  174. t.Fatalf("parse %s: DomainStrategy = %q", prefix, spec.DomainStrategy)
  175. }
  176. })
  177. }
  178. }