naive_self_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. //go:build with_naive_outbound
  2. package main
  3. import (
  4. "net/netip"
  5. "os"
  6. "strings"
  7. "testing"
  8. "github.com/sagernet/sing-box/common/tls"
  9. C "github.com/sagernet/sing-box/constant"
  10. "github.com/sagernet/sing-box/option"
  11. "github.com/sagernet/sing-box/protocol/naive"
  12. "github.com/sagernet/sing/common"
  13. "github.com/sagernet/sing/common/auth"
  14. "github.com/sagernet/sing/common/json/badoption"
  15. "github.com/sagernet/sing/common/network"
  16. "github.com/stretchr/testify/require"
  17. )
  18. func TestNaiveSelf(t *testing.T) {
  19. caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
  20. caPemContent, err := os.ReadFile(caPem)
  21. require.NoError(t, err)
  22. startInstance(t, option.Options{
  23. Inbounds: []option.Inbound{
  24. {
  25. Type: C.TypeMixed,
  26. Tag: "mixed-in",
  27. Options: &option.HTTPMixedInboundOptions{
  28. ListenOptions: option.ListenOptions{
  29. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  30. ListenPort: clientPort,
  31. },
  32. },
  33. },
  34. {
  35. Type: C.TypeNaive,
  36. Tag: "naive-in",
  37. Options: &option.NaiveInboundOptions{
  38. ListenOptions: option.ListenOptions{
  39. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  40. ListenPort: serverPort,
  41. },
  42. Users: []auth.User{
  43. {
  44. Username: "sekai",
  45. Password: "password",
  46. },
  47. },
  48. Network: network.NetworkTCP,
  49. InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
  50. TLS: &option.InboundTLSOptions{
  51. Enabled: true,
  52. ServerName: "example.org",
  53. CertificatePath: certPem,
  54. KeyPath: keyPem,
  55. },
  56. },
  57. },
  58. },
  59. },
  60. Outbounds: []option.Outbound{
  61. {
  62. Type: C.TypeDirect,
  63. },
  64. {
  65. Type: C.TypeNaive,
  66. Tag: "naive-out",
  67. Options: &option.NaiveOutboundOptions{
  68. ServerOptions: option.ServerOptions{
  69. Server: "127.0.0.1",
  70. ServerPort: serverPort,
  71. },
  72. Username: "sekai",
  73. Password: "password",
  74. OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
  75. TLS: &option.OutboundTLSOptions{
  76. Enabled: true,
  77. ServerName: "example.org",
  78. Certificate: []string{string(caPemContent)},
  79. },
  80. },
  81. },
  82. },
  83. },
  84. Route: &option.RouteOptions{
  85. Rules: []option.Rule{
  86. {
  87. Type: C.RuleTypeDefault,
  88. DefaultOptions: option.DefaultRule{
  89. RawDefaultRule: option.RawDefaultRule{
  90. Inbound: []string{"mixed-in"},
  91. },
  92. RuleAction: option.RuleAction{
  93. Action: C.RuleActionTypeRoute,
  94. RouteOptions: option.RouteActionOptions{
  95. Outbound: "naive-out",
  96. },
  97. },
  98. },
  99. },
  100. },
  101. },
  102. })
  103. testTCP(t, clientPort, testPort)
  104. }
  105. func TestNaiveSelfECH(t *testing.T) {
  106. caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
  107. caPemContent, err := os.ReadFile(caPem)
  108. require.NoError(t, err)
  109. echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org"))
  110. instance := startInstance(t, option.Options{
  111. Inbounds: []option.Inbound{
  112. {
  113. Type: C.TypeMixed,
  114. Tag: "mixed-in",
  115. Options: &option.HTTPMixedInboundOptions{
  116. ListenOptions: option.ListenOptions{
  117. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  118. ListenPort: clientPort,
  119. },
  120. },
  121. },
  122. {
  123. Type: C.TypeNaive,
  124. Tag: "naive-in",
  125. Options: &option.NaiveInboundOptions{
  126. ListenOptions: option.ListenOptions{
  127. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  128. ListenPort: serverPort,
  129. },
  130. Users: []auth.User{
  131. {
  132. Username: "sekai",
  133. Password: "password",
  134. },
  135. },
  136. Network: network.NetworkTCP,
  137. InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
  138. TLS: &option.InboundTLSOptions{
  139. Enabled: true,
  140. ServerName: "example.org",
  141. CertificatePath: certPem,
  142. KeyPath: keyPem,
  143. ECH: &option.InboundECHOptions{
  144. Enabled: true,
  145. Key: []string{echKey},
  146. },
  147. },
  148. },
  149. },
  150. },
  151. },
  152. Outbounds: []option.Outbound{
  153. {
  154. Type: C.TypeDirect,
  155. },
  156. {
  157. Type: C.TypeNaive,
  158. Tag: "naive-out",
  159. Options: &option.NaiveOutboundOptions{
  160. ServerOptions: option.ServerOptions{
  161. Server: "127.0.0.1",
  162. ServerPort: serverPort,
  163. },
  164. Username: "sekai",
  165. Password: "password",
  166. OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
  167. TLS: &option.OutboundTLSOptions{
  168. Enabled: true,
  169. ServerName: "example.org",
  170. Certificate: []string{string(caPemContent)},
  171. ECH: &option.OutboundECHOptions{
  172. Enabled: true,
  173. Config: []string{echConfig},
  174. },
  175. },
  176. },
  177. },
  178. },
  179. },
  180. Route: &option.RouteOptions{
  181. Rules: []option.Rule{
  182. {
  183. Type: C.RuleTypeDefault,
  184. DefaultOptions: option.DefaultRule{
  185. RawDefaultRule: option.RawDefaultRule{
  186. Inbound: []string{"mixed-in"},
  187. },
  188. RuleAction: option.RuleAction{
  189. Action: C.RuleActionTypeRoute,
  190. RouteOptions: option.RouteActionOptions{
  191. Outbound: "naive-out",
  192. },
  193. },
  194. },
  195. },
  196. },
  197. },
  198. })
  199. naiveOut, ok := instance.Outbound().Outbound("naive-out")
  200. require.True(t, ok)
  201. naiveOutbound := naiveOut.(*naive.Outbound)
  202. netLogPath := "/tmp/naive_ech_netlog.json"
  203. require.True(t, naiveOutbound.Client().Engine().StartNetLogToFile(netLogPath, true))
  204. defer naiveOutbound.Client().Engine().StopNetLog()
  205. testTCP(t, clientPort, testPort)
  206. naiveOutbound.Client().Engine().StopNetLog()
  207. logContent, err := os.ReadFile(netLogPath)
  208. require.NoError(t, err)
  209. logStr := string(logContent)
  210. require.True(t, strings.Contains(logStr, `"encrypted_client_hello":true`),
  211. "ECH should be accepted in TLS handshake. NetLog saved to: %s", netLogPath)
  212. }
  213. func TestNaiveSelfInsecureConcurrency(t *testing.T) {
  214. caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
  215. caPemContent, err := os.ReadFile(caPem)
  216. require.NoError(t, err)
  217. instance := startInstance(t, option.Options{
  218. Inbounds: []option.Inbound{
  219. {
  220. Type: C.TypeMixed,
  221. Tag: "mixed-in",
  222. Options: &option.HTTPMixedInboundOptions{
  223. ListenOptions: option.ListenOptions{
  224. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  225. ListenPort: clientPort,
  226. },
  227. },
  228. },
  229. {
  230. Type: C.TypeNaive,
  231. Tag: "naive-in",
  232. Options: &option.NaiveInboundOptions{
  233. ListenOptions: option.ListenOptions{
  234. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  235. ListenPort: serverPort,
  236. },
  237. Users: []auth.User{
  238. {
  239. Username: "sekai",
  240. Password: "password",
  241. },
  242. },
  243. Network: network.NetworkTCP,
  244. InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
  245. TLS: &option.InboundTLSOptions{
  246. Enabled: true,
  247. ServerName: "example.org",
  248. CertificatePath: certPem,
  249. KeyPath: keyPem,
  250. },
  251. },
  252. },
  253. },
  254. },
  255. Outbounds: []option.Outbound{
  256. {
  257. Type: C.TypeDirect,
  258. },
  259. {
  260. Type: C.TypeNaive,
  261. Tag: "naive-out",
  262. Options: &option.NaiveOutboundOptions{
  263. ServerOptions: option.ServerOptions{
  264. Server: "127.0.0.1",
  265. ServerPort: serverPort,
  266. },
  267. Username: "sekai",
  268. Password: "password",
  269. InsecureConcurrency: 3,
  270. OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
  271. TLS: &option.OutboundTLSOptions{
  272. Enabled: true,
  273. ServerName: "example.org",
  274. Certificate: []string{string(caPemContent)},
  275. },
  276. },
  277. },
  278. },
  279. },
  280. Route: &option.RouteOptions{
  281. Rules: []option.Rule{
  282. {
  283. Type: C.RuleTypeDefault,
  284. DefaultOptions: option.DefaultRule{
  285. RawDefaultRule: option.RawDefaultRule{
  286. Inbound: []string{"mixed-in"},
  287. },
  288. RuleAction: option.RuleAction{
  289. Action: C.RuleActionTypeRoute,
  290. RouteOptions: option.RouteActionOptions{
  291. Outbound: "naive-out",
  292. },
  293. },
  294. },
  295. },
  296. },
  297. },
  298. })
  299. naiveOut, ok := instance.Outbound().Outbound("naive-out")
  300. require.True(t, ok)
  301. naiveOutbound := naiveOut.(*naive.Outbound)
  302. netLogPath := "/tmp/naive_concurrency_netlog.json"
  303. require.True(t, naiveOutbound.Client().Engine().StartNetLogToFile(netLogPath, true))
  304. defer naiveOutbound.Client().Engine().StopNetLog()
  305. // Send multiple sequential connections to trigger round-robin
  306. // With insecure_concurrency=3, connections will be distributed to 3 pools
  307. for i := 0; i < 6; i++ {
  308. testTCP(t, clientPort, testPort)
  309. }
  310. naiveOutbound.Client().Engine().StopNetLog()
  311. // Verify NetLog contains multiple independent HTTP/2 sessions
  312. logContent, err := os.ReadFile(netLogPath)
  313. require.NoError(t, err)
  314. logStr := string(logContent)
  315. // Count HTTP2_SESSION_INITIALIZED events to verify connection pool isolation
  316. // NetLog stores event types as numeric IDs, HTTP2_SESSION_INITIALIZED = 249
  317. sessionCount := strings.Count(logStr, `"type":249`)
  318. require.GreaterOrEqual(t, sessionCount, 3,
  319. "Expected at least 3 HTTP/2 sessions with insecure_concurrency=3. NetLog: %s", netLogPath)
  320. }
  321. func TestNaiveSelfQUIC(t *testing.T) {
  322. caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
  323. caPemContent, err := os.ReadFile(caPem)
  324. require.NoError(t, err)
  325. startInstance(t, option.Options{
  326. Inbounds: []option.Inbound{
  327. {
  328. Type: C.TypeMixed,
  329. Tag: "mixed-in",
  330. Options: &option.HTTPMixedInboundOptions{
  331. ListenOptions: option.ListenOptions{
  332. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  333. ListenPort: clientPort,
  334. },
  335. },
  336. },
  337. {
  338. Type: C.TypeNaive,
  339. Tag: "naive-in",
  340. Options: &option.NaiveInboundOptions{
  341. ListenOptions: option.ListenOptions{
  342. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  343. ListenPort: serverPort,
  344. },
  345. Users: []auth.User{
  346. {
  347. Username: "sekai",
  348. Password: "password",
  349. },
  350. },
  351. Network: network.NetworkUDP,
  352. InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
  353. TLS: &option.InboundTLSOptions{
  354. Enabled: true,
  355. ServerName: "example.org",
  356. CertificatePath: certPem,
  357. KeyPath: keyPem,
  358. },
  359. },
  360. },
  361. },
  362. },
  363. Outbounds: []option.Outbound{
  364. {
  365. Type: C.TypeDirect,
  366. },
  367. {
  368. Type: C.TypeNaive,
  369. Tag: "naive-out",
  370. Options: &option.NaiveOutboundOptions{
  371. ServerOptions: option.ServerOptions{
  372. Server: "127.0.0.1",
  373. ServerPort: serverPort,
  374. },
  375. Username: "sekai",
  376. Password: "password",
  377. QUIC: true,
  378. OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
  379. TLS: &option.OutboundTLSOptions{
  380. Enabled: true,
  381. ServerName: "example.org",
  382. Certificate: []string{string(caPemContent)},
  383. },
  384. },
  385. },
  386. },
  387. },
  388. Route: &option.RouteOptions{
  389. Rules: []option.Rule{
  390. {
  391. Type: C.RuleTypeDefault,
  392. DefaultOptions: option.DefaultRule{
  393. RawDefaultRule: option.RawDefaultRule{
  394. Inbound: []string{"mixed-in"},
  395. },
  396. RuleAction: option.RuleAction{
  397. Action: C.RuleActionTypeRoute,
  398. RouteOptions: option.RouteActionOptions{
  399. Outbound: "naive-out",
  400. },
  401. },
  402. },
  403. },
  404. },
  405. },
  406. })
  407. testTCP(t, clientPort, testPort)
  408. }
  409. func TestNaiveSelfQUICCongestionControl(t *testing.T) {
  410. testCases := []struct {
  411. name string
  412. congestionControl string
  413. }{
  414. {"BBR", "bbr"},
  415. {"BBR2", "bbr2"},
  416. {"Cubic", "cubic"},
  417. {"Reno", "reno"},
  418. }
  419. for _, tc := range testCases {
  420. t.Run(tc.name, func(t *testing.T) {
  421. caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
  422. caPemContent, err := os.ReadFile(caPem)
  423. require.NoError(t, err)
  424. startInstance(t, option.Options{
  425. Inbounds: []option.Inbound{
  426. {
  427. Type: C.TypeMixed,
  428. Tag: "mixed-in",
  429. Options: &option.HTTPMixedInboundOptions{
  430. ListenOptions: option.ListenOptions{
  431. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  432. ListenPort: clientPort,
  433. },
  434. },
  435. },
  436. {
  437. Type: C.TypeNaive,
  438. Tag: "naive-in",
  439. Options: &option.NaiveInboundOptions{
  440. ListenOptions: option.ListenOptions{
  441. Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
  442. ListenPort: serverPort,
  443. },
  444. Users: []auth.User{
  445. {
  446. Username: "sekai",
  447. Password: "password",
  448. },
  449. },
  450. Network: network.NetworkUDP,
  451. QUICCongestionControl: tc.congestionControl,
  452. InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
  453. TLS: &option.InboundTLSOptions{
  454. Enabled: true,
  455. ServerName: "example.org",
  456. CertificatePath: certPem,
  457. KeyPath: keyPem,
  458. },
  459. },
  460. },
  461. },
  462. },
  463. Outbounds: []option.Outbound{
  464. {
  465. Type: C.TypeDirect,
  466. },
  467. {
  468. Type: C.TypeNaive,
  469. Tag: "naive-out",
  470. Options: &option.NaiveOutboundOptions{
  471. ServerOptions: option.ServerOptions{
  472. Server: "127.0.0.1",
  473. ServerPort: serverPort,
  474. },
  475. Username: "sekai",
  476. Password: "password",
  477. QUIC: true,
  478. QUICCongestionControl: tc.congestionControl,
  479. OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
  480. TLS: &option.OutboundTLSOptions{
  481. Enabled: true,
  482. ServerName: "example.org",
  483. Certificate: []string{string(caPemContent)},
  484. },
  485. },
  486. },
  487. },
  488. },
  489. Route: &option.RouteOptions{
  490. Rules: []option.Rule{
  491. {
  492. Type: C.RuleTypeDefault,
  493. DefaultOptions: option.DefaultRule{
  494. RawDefaultRule: option.RawDefaultRule{
  495. Inbound: []string{"mixed-in"},
  496. },
  497. RuleAction: option.RuleAction{
  498. Action: C.RuleActionTypeRoute,
  499. RouteOptions: option.RouteActionOptions{
  500. Outbound: "naive-out",
  501. },
  502. },
  503. },
  504. },
  505. },
  506. },
  507. })
  508. testTCP(t, clientPort, testPort)
  509. })
  510. }
  511. }