naive_self_test.go 14 KB

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