basic.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. package basic
  2. import (
  3. "crypto/tls"
  4. "fmt"
  5. "strings"
  6. "time"
  7. "github.com/onsi/ginkgo/v2"
  8. "github.com/fatedier/frp/pkg/transport"
  9. "github.com/fatedier/frp/test/e2e/framework"
  10. "github.com/fatedier/frp/test/e2e/framework/consts"
  11. "github.com/fatedier/frp/test/e2e/mock/server/httpserver"
  12. "github.com/fatedier/frp/test/e2e/mock/server/streamserver"
  13. "github.com/fatedier/frp/test/e2e/pkg/port"
  14. "github.com/fatedier/frp/test/e2e/pkg/request"
  15. )
  16. var _ = ginkgo.Describe("[Feature: Basic]", func() {
  17. f := framework.NewDefaultFramework()
  18. ginkgo.Describe("TCP && UDP", func() {
  19. types := []string{"tcp", "udp"}
  20. for _, t := range types {
  21. proxyType := t
  22. ginkgo.It(fmt.Sprintf("Expose a %s echo server", strings.ToUpper(proxyType)), func() {
  23. serverConf := consts.DefaultServerConfig
  24. clientConf := consts.DefaultClientConfig
  25. localPortName := ""
  26. protocol := "tcp"
  27. switch proxyType {
  28. case "tcp":
  29. localPortName = framework.TCPEchoServerPort
  30. protocol = "tcp"
  31. case "udp":
  32. localPortName = framework.UDPEchoServerPort
  33. protocol = "udp"
  34. }
  35. getProxyConf := func(proxyName string, portName string, extra string) string {
  36. return fmt.Sprintf(`
  37. [[proxies]]
  38. name = "%s"
  39. type = "%s"
  40. localPort = {{ .%s }}
  41. remotePort = {{ .%s }}
  42. `+extra, proxyName, proxyType, localPortName, portName)
  43. }
  44. tests := []struct {
  45. proxyName string
  46. portName string
  47. extraConfig string
  48. }{
  49. {
  50. proxyName: "normal",
  51. portName: port.GenName("Normal"),
  52. },
  53. {
  54. proxyName: "with-encryption",
  55. portName: port.GenName("WithEncryption"),
  56. extraConfig: "transport.useEncryption = true",
  57. },
  58. {
  59. proxyName: "with-compression",
  60. portName: port.GenName("WithCompression"),
  61. extraConfig: "transport.useCompression = true",
  62. },
  63. {
  64. proxyName: "with-encryption-and-compression",
  65. portName: port.GenName("WithEncryptionAndCompression"),
  66. extraConfig: `
  67. transport.useEncryption = true
  68. transport.useCompression = true
  69. `,
  70. },
  71. }
  72. // build all client config
  73. for _, test := range tests {
  74. clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
  75. }
  76. // run frps and frpc
  77. f.RunProcesses([]string{serverConf}, []string{clientConf})
  78. for _, test := range tests {
  79. framework.NewRequestExpect(f).
  80. Protocol(protocol).
  81. PortName(test.portName).
  82. Explain(test.proxyName).
  83. Ensure()
  84. }
  85. })
  86. }
  87. })
  88. ginkgo.Describe("HTTP", func() {
  89. ginkgo.It("proxy to HTTP server", func() {
  90. serverConf := consts.DefaultServerConfig
  91. vhostHTTPPort := f.AllocPort()
  92. serverConf += fmt.Sprintf(`
  93. vhostHTTPPort = %d
  94. `, vhostHTTPPort)
  95. clientConf := consts.DefaultClientConfig
  96. getProxyConf := func(proxyName string, customDomains string, extra string) string {
  97. return fmt.Sprintf(`
  98. [[proxies]]
  99. name = "%s"
  100. type = "http"
  101. localPort = {{ .%s }}
  102. customDomains = %s
  103. `+extra, proxyName, framework.HTTPSimpleServerPort, customDomains)
  104. }
  105. tests := []struct {
  106. proxyName string
  107. customDomains string
  108. extraConfig string
  109. }{
  110. {
  111. proxyName: "normal",
  112. },
  113. {
  114. proxyName: "with-encryption",
  115. extraConfig: "transport.useEncryption = true",
  116. },
  117. {
  118. proxyName: "with-compression",
  119. extraConfig: "transport.useCompression = true",
  120. },
  121. {
  122. proxyName: "with-encryption-and-compression",
  123. extraConfig: `
  124. transport.useEncryption = true
  125. transport.useCompression = true
  126. `,
  127. },
  128. {
  129. proxyName: "multiple-custom-domains",
  130. customDomains: `["a.example.com", "b.example.com"]`,
  131. },
  132. }
  133. // build all client config
  134. for i, test := range tests {
  135. if tests[i].customDomains == "" {
  136. tests[i].customDomains = fmt.Sprintf(`["%s"]`, test.proxyName+".example.com")
  137. }
  138. clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
  139. }
  140. // run frps and frpc
  141. f.RunProcesses([]string{serverConf}, []string{clientConf})
  142. for _, test := range tests {
  143. for _, domain := range strings.Split(test.customDomains, ",") {
  144. domain = strings.TrimSpace(domain)
  145. domain = strings.TrimLeft(domain, "[\"")
  146. domain = strings.TrimRight(domain, "]\"")
  147. framework.NewRequestExpect(f).
  148. Explain(test.proxyName + "-" + domain).
  149. Port(vhostHTTPPort).
  150. RequestModify(func(r *request.Request) {
  151. r.HTTP().HTTPHost(domain)
  152. }).
  153. Ensure()
  154. }
  155. }
  156. // not exist host
  157. framework.NewRequestExpect(f).
  158. Explain("not exist host").
  159. Port(vhostHTTPPort).
  160. RequestModify(func(r *request.Request) {
  161. r.HTTP().HTTPHost("not-exist.example.com")
  162. }).
  163. Ensure(framework.ExpectResponseCode(404))
  164. })
  165. })
  166. ginkgo.Describe("HTTPS", func() {
  167. ginkgo.It("proxy to HTTPS server", func() {
  168. serverConf := consts.DefaultServerConfig
  169. vhostHTTPSPort := f.AllocPort()
  170. serverConf += fmt.Sprintf(`
  171. vhostHTTPSPort = %d
  172. `, vhostHTTPSPort)
  173. localPort := f.AllocPort()
  174. clientConf := consts.DefaultClientConfig
  175. getProxyConf := func(proxyName string, customDomains string, extra string) string {
  176. return fmt.Sprintf(`
  177. [[proxies]]
  178. name = "%s"
  179. type = "https"
  180. localPort = %d
  181. customDomains = %s
  182. `+extra, proxyName, localPort, customDomains)
  183. }
  184. tests := []struct {
  185. proxyName string
  186. customDomains string
  187. extraConfig string
  188. }{
  189. {
  190. proxyName: "normal",
  191. },
  192. {
  193. proxyName: "with-encryption",
  194. extraConfig: "transport.useEncryption = true",
  195. },
  196. {
  197. proxyName: "with-compression",
  198. extraConfig: "transport.useCompression = true",
  199. },
  200. {
  201. proxyName: "with-encryption-and-compression",
  202. extraConfig: `
  203. transport.useEncryption = true
  204. transport.useCompression = true
  205. `,
  206. },
  207. {
  208. proxyName: "multiple-custom-domains",
  209. customDomains: `["a.example.com", "b.example.com"]`,
  210. },
  211. }
  212. // build all client config
  213. for i, test := range tests {
  214. if tests[i].customDomains == "" {
  215. tests[i].customDomains = fmt.Sprintf(`["%s"]`, test.proxyName+".example.com")
  216. }
  217. clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
  218. }
  219. // run frps and frpc
  220. f.RunProcesses([]string{serverConf}, []string{clientConf})
  221. tlsConfig, err := transport.NewServerTLSConfig("", "", "")
  222. framework.ExpectNoError(err)
  223. localServer := httpserver.New(
  224. httpserver.WithBindPort(localPort),
  225. httpserver.WithTLSConfig(tlsConfig),
  226. httpserver.WithResponse([]byte("test")),
  227. )
  228. f.RunServer("", localServer)
  229. for _, test := range tests {
  230. for _, domain := range strings.Split(test.customDomains, ",") {
  231. domain = strings.TrimSpace(domain)
  232. domain = strings.TrimLeft(domain, "[\"")
  233. domain = strings.TrimRight(domain, "]\"")
  234. framework.NewRequestExpect(f).
  235. Explain(test.proxyName + "-" + domain).
  236. Port(vhostHTTPSPort).
  237. RequestModify(func(r *request.Request) {
  238. r.HTTPS().HTTPHost(domain).TLSConfig(&tls.Config{
  239. ServerName: domain,
  240. InsecureSkipVerify: true,
  241. })
  242. }).
  243. ExpectResp([]byte("test")).
  244. Ensure()
  245. }
  246. }
  247. // not exist host
  248. notExistDomain := "not-exist.example.com"
  249. framework.NewRequestExpect(f).
  250. Explain("not exist host").
  251. Port(vhostHTTPSPort).
  252. RequestModify(func(r *request.Request) {
  253. r.HTTPS().HTTPHost(notExistDomain).TLSConfig(&tls.Config{
  254. ServerName: notExistDomain,
  255. InsecureSkipVerify: true,
  256. })
  257. }).
  258. ExpectError(true).
  259. Ensure()
  260. })
  261. })
  262. ginkgo.Describe("STCP && SUDP && XTCP", func() {
  263. types := []string{"stcp", "sudp", "xtcp"}
  264. for _, t := range types {
  265. proxyType := t
  266. ginkgo.It(fmt.Sprintf("Expose echo server with %s", strings.ToUpper(proxyType)), func() {
  267. serverConf := consts.DefaultServerConfig
  268. clientServerConf := consts.DefaultClientConfig + "\nuser = \"user1\""
  269. clientVisitorConf := consts.DefaultClientConfig + "\nuser = \"user1\""
  270. clientUser2VisitorConf := consts.DefaultClientConfig + "\nuser = \"user2\""
  271. localPortName := ""
  272. protocol := "tcp"
  273. switch proxyType {
  274. case "stcp":
  275. localPortName = framework.TCPEchoServerPort
  276. protocol = "tcp"
  277. case "sudp":
  278. localPortName = framework.UDPEchoServerPort
  279. protocol = "udp"
  280. case "xtcp":
  281. localPortName = framework.TCPEchoServerPort
  282. protocol = "tcp"
  283. ginkgo.Skip("stun server is not stable")
  284. }
  285. correctSK := "abc"
  286. wrongSK := "123"
  287. getProxyServerConf := func(proxyName string, extra string) string {
  288. return fmt.Sprintf(`
  289. [[proxies]]
  290. name = "%s"
  291. type = "%s"
  292. secretKey = "%s"
  293. localPort = {{ .%s }}
  294. `+extra, proxyName, proxyType, correctSK, localPortName)
  295. }
  296. getProxyVisitorConf := func(proxyName string, portName, visitorSK, extra string) string {
  297. return fmt.Sprintf(`
  298. [[visitors]]
  299. name = "%s"
  300. type = "%s"
  301. serverName = "%s"
  302. secretKey = "%s"
  303. bindPort = {{ .%s }}
  304. `+extra, proxyName, proxyType, proxyName, visitorSK, portName)
  305. }
  306. tests := []struct {
  307. proxyName string
  308. bindPortName string
  309. visitorSK string
  310. commonExtraConfig string
  311. proxyExtraConfig string
  312. visitorExtraConfig string
  313. expectError bool
  314. deployUser2Client bool
  315. // skipXTCP is used to skip xtcp test case
  316. skipXTCP bool
  317. }{
  318. {
  319. proxyName: "normal",
  320. bindPortName: port.GenName("Normal"),
  321. visitorSK: correctSK,
  322. skipXTCP: true,
  323. },
  324. {
  325. proxyName: "with-encryption",
  326. bindPortName: port.GenName("WithEncryption"),
  327. visitorSK: correctSK,
  328. commonExtraConfig: "transport.useEncryption = true",
  329. skipXTCP: true,
  330. },
  331. {
  332. proxyName: "with-compression",
  333. bindPortName: port.GenName("WithCompression"),
  334. visitorSK: correctSK,
  335. commonExtraConfig: "transport.useCompression = true",
  336. skipXTCP: true,
  337. },
  338. {
  339. proxyName: "with-encryption-and-compression",
  340. bindPortName: port.GenName("WithEncryptionAndCompression"),
  341. visitorSK: correctSK,
  342. commonExtraConfig: `
  343. transport.useEncryption = true
  344. transport.useCompression = true
  345. `,
  346. skipXTCP: true,
  347. },
  348. {
  349. proxyName: "with-error-sk",
  350. bindPortName: port.GenName("WithErrorSK"),
  351. visitorSK: wrongSK,
  352. expectError: true,
  353. },
  354. {
  355. proxyName: "allowed-user",
  356. bindPortName: port.GenName("AllowedUser"),
  357. visitorSK: correctSK,
  358. proxyExtraConfig: `allowUsers = ["another", "user2"]`,
  359. visitorExtraConfig: `serverUser = "user1"`,
  360. deployUser2Client: true,
  361. },
  362. {
  363. proxyName: "not-allowed-user",
  364. bindPortName: port.GenName("NotAllowedUser"),
  365. visitorSK: correctSK,
  366. proxyExtraConfig: `allowUsers = ["invalid"]`,
  367. visitorExtraConfig: `serverUser = "user1"`,
  368. expectError: true,
  369. },
  370. {
  371. proxyName: "allow-all",
  372. bindPortName: port.GenName("AllowAll"),
  373. visitorSK: correctSK,
  374. proxyExtraConfig: `allowUsers = ["*"]`,
  375. visitorExtraConfig: `serverUser = "user1"`,
  376. deployUser2Client: true,
  377. },
  378. }
  379. // build all client config
  380. for _, test := range tests {
  381. clientServerConf += getProxyServerConf(test.proxyName, test.commonExtraConfig+"\n"+test.proxyExtraConfig) + "\n"
  382. }
  383. for _, test := range tests {
  384. config := getProxyVisitorConf(
  385. test.proxyName, test.bindPortName, test.visitorSK, test.commonExtraConfig+"\n"+test.visitorExtraConfig,
  386. ) + "\n"
  387. if test.deployUser2Client {
  388. clientUser2VisitorConf += config
  389. } else {
  390. clientVisitorConf += config
  391. }
  392. }
  393. // run frps and frpc
  394. f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf, clientUser2VisitorConf})
  395. for _, test := range tests {
  396. timeout := time.Second
  397. if t == "xtcp" {
  398. if test.skipXTCP {
  399. continue
  400. }
  401. timeout = 10 * time.Second
  402. }
  403. framework.NewRequestExpect(f).
  404. RequestModify(func(r *request.Request) {
  405. r.Timeout(timeout)
  406. }).
  407. Protocol(protocol).
  408. PortName(test.bindPortName).
  409. Explain(test.proxyName).
  410. ExpectError(test.expectError).
  411. Ensure()
  412. }
  413. })
  414. }
  415. })
  416. ginkgo.Describe("TCPMUX", func() {
  417. ginkgo.It("Type tcpmux", func() {
  418. serverConf := consts.DefaultServerConfig
  419. clientConf := consts.DefaultClientConfig
  420. tcpmuxHTTPConnectPortName := port.GenName("TCPMUX")
  421. serverConf += fmt.Sprintf(`
  422. tcpmuxHTTPConnectPort = {{ .%s }}
  423. `, tcpmuxHTTPConnectPortName)
  424. getProxyConf := func(proxyName string, extra string) string {
  425. return fmt.Sprintf(`
  426. [[proxies]]
  427. name = "%s"
  428. type = "tcpmux"
  429. multiplexer = "httpconnect"
  430. localPort = {{ .%s }}
  431. customDomains = ["%s"]
  432. `+extra, proxyName, port.GenName(proxyName), proxyName)
  433. }
  434. tests := []struct {
  435. proxyName string
  436. extraConfig string
  437. }{
  438. {
  439. proxyName: "normal",
  440. },
  441. {
  442. proxyName: "with-encryption",
  443. extraConfig: "transport.useEncryption = true",
  444. },
  445. {
  446. proxyName: "with-compression",
  447. extraConfig: "transport.useCompression = true",
  448. },
  449. {
  450. proxyName: "with-encryption-and-compression",
  451. extraConfig: `
  452. transport.useEncryption = true
  453. transport.useCompression = true
  454. `,
  455. },
  456. }
  457. // build all client config
  458. for _, test := range tests {
  459. clientConf += getProxyConf(test.proxyName, test.extraConfig) + "\n"
  460. localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(f.AllocPort()), streamserver.WithRespContent([]byte(test.proxyName)))
  461. f.RunServer(port.GenName(test.proxyName), localServer)
  462. }
  463. // run frps and frpc
  464. f.RunProcesses([]string{serverConf}, []string{clientConf})
  465. // Request without HTTP connect should get error
  466. framework.NewRequestExpect(f).
  467. PortName(tcpmuxHTTPConnectPortName).
  468. ExpectError(true).
  469. Explain("request without HTTP connect expect error").
  470. Ensure()
  471. proxyURL := fmt.Sprintf("http://127.0.0.1:%d", f.PortByName(tcpmuxHTTPConnectPortName))
  472. // Request with incorrect connect hostname
  473. framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
  474. r.Addr("invalid").Proxy(proxyURL)
  475. }).ExpectError(true).Explain("request without HTTP connect expect error").Ensure()
  476. // Request with correct connect hostname
  477. for _, test := range tests {
  478. framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
  479. r.Addr(test.proxyName).Proxy(proxyURL)
  480. }).ExpectResp([]byte(test.proxyName)).Explain(test.proxyName).Ensure()
  481. }
  482. })
  483. })
  484. })