shadowtls_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. package main
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "net"
  6. "net/http"
  7. "net/netip"
  8. "testing"
  9. C "github.com/sagernet/sing-box/constant"
  10. "github.com/sagernet/sing-box/option"
  11. "github.com/sagernet/sing-shadowsocks/shadowaead_2022"
  12. "github.com/sagernet/sing/common"
  13. F "github.com/sagernet/sing/common/format"
  14. "github.com/sagernet/sing/common/json/badoption"
  15. "github.com/stretchr/testify/require"
  16. )
  17. func TestShadowTLS(t *testing.T) {
  18. t.Run("v1", func(t *testing.T) {
  19. testShadowTLS(t, 1, "", false, option.ShadowTLSWildcardSNIOff)
  20. })
  21. t.Run("v2", func(t *testing.T) {
  22. testShadowTLS(t, 2, "hello", false, option.ShadowTLSWildcardSNIOff)
  23. })
  24. t.Run("v3", func(t *testing.T) {
  25. testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIOff)
  26. })
  27. t.Run("v2-utls", func(t *testing.T) {
  28. testShadowTLS(t, 2, "hello", true, option.ShadowTLSWildcardSNIOff)
  29. })
  30. t.Run("v3-utls", func(t *testing.T) {
  31. testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIOff)
  32. })
  33. t.Run("v3-wildcard-sni-authed", func(t *testing.T) {
  34. testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIAuthed)
  35. })
  36. t.Run("v3-wildcard-sni-all", func(t *testing.T) {
  37. testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIAll)
  38. })
  39. t.Run("v3-wildcard-sni-authed-utls", func(t *testing.T) {
  40. testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIAll)
  41. })
  42. t.Run("v3-wildcard-sni-all-utls", func(t *testing.T) {
  43. testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIAll)
  44. })
  45. }
  46. func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool, wildcardSNI option.WildcardSNI) {
  47. method := shadowaead_2022.List[0]
  48. ssPassword := mkBase64(t, 16)
  49. var clientServerName string
  50. if wildcardSNI != option.ShadowTLSWildcardSNIOff {
  51. clientServerName = "cloudflare.com"
  52. } else {
  53. clientServerName = "google.com"
  54. }
  55. startInstance(t, option.Options{
  56. Inbounds: []option.Inbound{
  57. {
  58. Type: C.TypeMixed,
  59. Options: &option.HTTPMixedInboundOptions{
  60. ListenOptions: option.ListenOptions{
  61. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  62. ListenPort: clientPort,
  63. },
  64. },
  65. },
  66. {
  67. Type: C.TypeShadowTLS,
  68. Tag: "in",
  69. Options: &option.ShadowTLSInboundOptions{
  70. ListenOptions: option.ListenOptions{
  71. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  72. ListenPort: serverPort,
  73. InboundOptions: option.InboundOptions{
  74. Detour: "detour",
  75. },
  76. },
  77. Handshake: option.ShadowTLSHandshakeOptions{
  78. ServerOptions: option.ServerOptions{
  79. Server: "google.com",
  80. ServerPort: 443,
  81. },
  82. },
  83. Version: version,
  84. Password: password,
  85. Users: []option.ShadowTLSUser{{Password: password}},
  86. WildcardSNI: wildcardSNI,
  87. },
  88. },
  89. {
  90. Type: C.TypeShadowsocks,
  91. Tag: "detour",
  92. Options: &option.ShadowsocksInboundOptions{
  93. ListenOptions: option.ListenOptions{
  94. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  95. ListenPort: otherPort,
  96. },
  97. Method: method,
  98. Password: ssPassword,
  99. },
  100. },
  101. },
  102. Outbounds: []option.Outbound{
  103. {
  104. Type: C.TypeShadowsocks,
  105. Options: &option.ShadowsocksOutboundOptions{
  106. Method: method,
  107. Password: ssPassword,
  108. DialerOptions: option.DialerOptions{
  109. Detour: "detour",
  110. },
  111. },
  112. },
  113. {
  114. Type: C.TypeShadowTLS,
  115. Tag: "detour",
  116. Options: &option.ShadowTLSOutboundOptions{
  117. ServerOptions: option.ServerOptions{
  118. Server: "127.0.0.1",
  119. ServerPort: serverPort,
  120. },
  121. OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
  122. TLS: &option.OutboundTLSOptions{
  123. Enabled: true,
  124. ServerName: clientServerName,
  125. UTLS: &option.OutboundUTLSOptions{
  126. Enabled: utlsEanbled,
  127. },
  128. },
  129. },
  130. Version: version,
  131. Password: password,
  132. },
  133. },
  134. {
  135. Type: C.TypeDirect,
  136. Tag: "direct",
  137. },
  138. },
  139. Route: &option.RouteOptions{
  140. Rules: []option.Rule{
  141. {
  142. Type: C.RuleTypeDefault,
  143. DefaultOptions: option.DefaultRule{
  144. RawDefaultRule: option.RawDefaultRule{
  145. Inbound: []string{"detour"},
  146. },
  147. RuleAction: option.RuleAction{
  148. Action: C.RuleActionTypeRoute,
  149. RouteOptions: option.RouteActionOptions{
  150. Outbound: "direct",
  151. },
  152. },
  153. },
  154. },
  155. },
  156. },
  157. })
  158. testTCP(t, clientPort, testPort)
  159. }
  160. func TestShadowTLSFallback(t *testing.T) {
  161. startInstance(t, option.Options{
  162. Inbounds: []option.Inbound{
  163. {
  164. Type: C.TypeShadowTLS,
  165. Options: &option.ShadowTLSInboundOptions{
  166. ListenOptions: option.ListenOptions{
  167. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  168. ListenPort: serverPort,
  169. },
  170. Handshake: option.ShadowTLSHandshakeOptions{
  171. ServerOptions: option.ServerOptions{
  172. Server: "bing.com",
  173. ServerPort: 443,
  174. },
  175. },
  176. Version: 3,
  177. Users: []option.ShadowTLSUser{
  178. {Password: "hello"},
  179. },
  180. },
  181. },
  182. },
  183. })
  184. client := &http.Client{
  185. Transport: &http.Transport{
  186. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  187. var d net.Dialer
  188. return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort))
  189. },
  190. },
  191. }
  192. response, err := client.Get("https://bing.com")
  193. require.NoError(t, err)
  194. require.Equal(t, response.StatusCode, 200)
  195. response.Body.Close()
  196. client.CloseIdleConnections()
  197. }
  198. func TestShadowTLSFallbackWildcardAll(t *testing.T) {
  199. startInstance(t, option.Options{
  200. Inbounds: []option.Inbound{
  201. {
  202. Type: C.TypeShadowTLS,
  203. Options: &option.ShadowTLSInboundOptions{
  204. ListenOptions: option.ListenOptions{
  205. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  206. ListenPort: serverPort,
  207. },
  208. Version: 3,
  209. Users: []option.ShadowTLSUser{
  210. {Password: "hello"},
  211. },
  212. WildcardSNI: option.ShadowTLSWildcardSNIAll,
  213. },
  214. },
  215. },
  216. })
  217. client := &http.Client{
  218. Transport: &http.Transport{
  219. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  220. var d net.Dialer
  221. return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort))
  222. },
  223. },
  224. }
  225. response, err := client.Get("https://www.bing.com")
  226. require.NoError(t, err)
  227. require.Equal(t, response.StatusCode, 200)
  228. response.Body.Close()
  229. client.CloseIdleConnections()
  230. }
  231. func TestShadowTLSFallbackWildcardAuthedFail(t *testing.T) {
  232. startInstance(t, option.Options{
  233. Inbounds: []option.Inbound{
  234. {
  235. Type: C.TypeShadowTLS,
  236. Options: &option.ShadowTLSInboundOptions{
  237. ListenOptions: option.ListenOptions{
  238. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  239. ListenPort: serverPort,
  240. },
  241. Handshake: option.ShadowTLSHandshakeOptions{
  242. ServerOptions: option.ServerOptions{
  243. Server: "bing.com",
  244. ServerPort: 443,
  245. },
  246. },
  247. Version: 3,
  248. Users: []option.ShadowTLSUser{
  249. {Password: "hello"},
  250. },
  251. WildcardSNI: option.ShadowTLSWildcardSNIAuthed,
  252. },
  253. },
  254. },
  255. })
  256. client := &http.Client{
  257. Transport: &http.Transport{
  258. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  259. var d net.Dialer
  260. return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort))
  261. },
  262. },
  263. }
  264. _, err := client.Get("https://baidu.com")
  265. expected := &tls.CertificateVerificationError{}
  266. require.ErrorAs(t, err, &expected)
  267. client.CloseIdleConnections()
  268. }
  269. func TestShadowTLSFallbackWildcardOffFail(t *testing.T) {
  270. startInstance(t, option.Options{
  271. Inbounds: []option.Inbound{
  272. {
  273. Type: C.TypeShadowTLS,
  274. Options: &option.ShadowTLSInboundOptions{
  275. ListenOptions: option.ListenOptions{
  276. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  277. ListenPort: serverPort,
  278. },
  279. Handshake: option.ShadowTLSHandshakeOptions{
  280. ServerOptions: option.ServerOptions{
  281. Server: "bing.com",
  282. ServerPort: 443,
  283. },
  284. },
  285. Version: 3,
  286. Users: []option.ShadowTLSUser{
  287. {Password: "hello"},
  288. },
  289. WildcardSNI: option.ShadowTLSWildcardSNIOff,
  290. },
  291. },
  292. },
  293. })
  294. client := &http.Client{
  295. Transport: &http.Transport{
  296. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  297. var d net.Dialer
  298. return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort))
  299. },
  300. },
  301. }
  302. _, err := client.Get("https://baidu.com")
  303. expected := &tls.CertificateVerificationError{}
  304. require.ErrorAs(t, err, &expected)
  305. client.CloseIdleConnections()
  306. }
  307. func TestShadowTLSInbound(t *testing.T) {
  308. method := shadowaead_2022.List[0]
  309. password := mkBase64(t, 16)
  310. startDockerContainer(t, DockerOptions{
  311. Image: ImageShadowTLS,
  312. Ports: []uint16{serverPort, otherPort},
  313. EntryPoint: "shadow-tls",
  314. Cmd: []string{"--v3", "--threads", "1", "client", "--listen", "0.0.0.0:" + F.ToString(otherPort), "--server", "127.0.0.1:" + F.ToString(serverPort), "--sni", "google.com", "--password", password},
  315. })
  316. startInstance(t, option.Options{
  317. Inbounds: []option.Inbound{
  318. {
  319. Type: C.TypeMixed,
  320. Tag: "in",
  321. Options: &option.HTTPMixedInboundOptions{
  322. ListenOptions: option.ListenOptions{
  323. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  324. ListenPort: clientPort,
  325. },
  326. },
  327. },
  328. {
  329. Type: C.TypeShadowTLS,
  330. Options: &option.ShadowTLSInboundOptions{
  331. ListenOptions: option.ListenOptions{
  332. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  333. ListenPort: serverPort,
  334. InboundOptions: option.InboundOptions{
  335. Detour: "detour",
  336. },
  337. },
  338. Handshake: option.ShadowTLSHandshakeOptions{
  339. ServerOptions: option.ServerOptions{
  340. Server: "google.com",
  341. ServerPort: 443,
  342. },
  343. },
  344. Version: 3,
  345. Users: []option.ShadowTLSUser{
  346. {Password: password},
  347. },
  348. },
  349. },
  350. {
  351. Type: C.TypeShadowsocks,
  352. Tag: "detour",
  353. Options: &option.ShadowsocksInboundOptions{
  354. ListenOptions: option.ListenOptions{
  355. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  356. },
  357. Method: method,
  358. Password: password,
  359. },
  360. },
  361. },
  362. Outbounds: []option.Outbound{
  363. {
  364. Type: C.TypeDirect,
  365. },
  366. {
  367. Type: C.TypeShadowsocks,
  368. Tag: "out",
  369. Options: &option.ShadowsocksOutboundOptions{
  370. ServerOptions: option.ServerOptions{
  371. Server: "127.0.0.1",
  372. ServerPort: otherPort,
  373. },
  374. Method: method,
  375. Password: password,
  376. },
  377. },
  378. },
  379. Route: &option.RouteOptions{
  380. Rules: []option.Rule{
  381. {
  382. Type: C.RuleTypeDefault,
  383. DefaultOptions: option.DefaultRule{
  384. RawDefaultRule: option.RawDefaultRule{
  385. Inbound: []string{"in"},
  386. },
  387. RuleAction: option.RuleAction{
  388. Action: C.RuleActionTypeRoute,
  389. RouteOptions: option.RouteActionOptions{
  390. Outbound: "out",
  391. },
  392. },
  393. },
  394. },
  395. },
  396. },
  397. })
  398. testTCP(t, clientPort, testPort)
  399. }
  400. func TestShadowTLSOutbound(t *testing.T) {
  401. method := shadowaead_2022.List[0]
  402. password := mkBase64(t, 16)
  403. startDockerContainer(t, DockerOptions{
  404. Image: ImageShadowTLS,
  405. Ports: []uint16{serverPort, otherPort},
  406. EntryPoint: "shadow-tls",
  407. Cmd: []string{"--v3", "--threads", "1", "server", "--listen", "0.0.0.0:" + F.ToString(serverPort), "--server", "127.0.0.1:" + F.ToString(otherPort), "--tls", "google.com:443", "--password", "hello"},
  408. Env: []string{"RUST_LOG=trace"},
  409. })
  410. startInstance(t, option.Options{
  411. Inbounds: []option.Inbound{
  412. {
  413. Type: C.TypeMixed,
  414. Options: &option.HTTPMixedInboundOptions{
  415. ListenOptions: option.ListenOptions{
  416. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  417. ListenPort: clientPort,
  418. },
  419. },
  420. },
  421. {
  422. Type: C.TypeShadowsocks,
  423. Tag: "detour",
  424. Options: &option.ShadowsocksInboundOptions{
  425. ListenOptions: option.ListenOptions{
  426. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  427. ListenPort: otherPort,
  428. },
  429. Method: method,
  430. Password: password,
  431. },
  432. },
  433. },
  434. Outbounds: []option.Outbound{
  435. {
  436. Type: C.TypeShadowsocks,
  437. Options: &option.ShadowsocksOutboundOptions{
  438. Method: method,
  439. Password: password,
  440. DialerOptions: option.DialerOptions{
  441. Detour: "detour",
  442. },
  443. },
  444. },
  445. {
  446. Type: C.TypeShadowTLS,
  447. Tag: "detour",
  448. Options: &option.ShadowTLSOutboundOptions{
  449. ServerOptions: option.ServerOptions{
  450. Server: "127.0.0.1",
  451. ServerPort: serverPort,
  452. },
  453. OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
  454. TLS: &option.OutboundTLSOptions{
  455. Enabled: true,
  456. ServerName: "google.com",
  457. },
  458. },
  459. Version: 3,
  460. Password: "hello",
  461. },
  462. },
  463. {
  464. Type: C.TypeDirect,
  465. Tag: "direct",
  466. },
  467. },
  468. Route: &option.RouteOptions{
  469. Rules: []option.Rule{
  470. {
  471. Type: C.RuleTypeDefault,
  472. DefaultOptions: option.DefaultRule{
  473. RawDefaultRule: option.RawDefaultRule{
  474. Inbound: []string{"detour"},
  475. },
  476. RuleAction: option.RuleAction{
  477. Action: C.RuleActionTypeRoute,
  478. RouteOptions: option.RouteActionOptions{
  479. Outbound: "direct",
  480. },
  481. },
  482. },
  483. },
  484. },
  485. },
  486. })
  487. testTCP(t, clientPort, testPort)
  488. }