package main import ( "bytes" "fmt" "io" "math/rand" "net" "net/http" "net/http/httptest" "net/http/httptrace" "os" "strconv" "strings" "sync" "time" "github.com/onsi/gomega/ghttp" "github.com/onsi/gomega/gmeasure" . "fd.io/hs-test/infra" . "github.com/onsi/ginkgo/v2" ) func init() { RegisterVethTests(HttpCliTest, HttpCliConnectErrorTest) RegisterSoloVethTests(HttpClientGetMemLeakTest) RegisterNoTopoTests(HeaderServerTest, HttpPersistentConnectionTest, HttpPipeliningTest, HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest, HttpCliBadRequestTest, HttpStaticBuildInUrlGetIfStatsTest, HttpStaticBuildInUrlPostIfStatsTest, HttpInvalidRequestLineTest, HttpMethodNotImplementedTest, HttpInvalidHeadersTest, HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest, HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest, HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest, HttpHeadersTest, HttpStaticFileHandlerTest, HttpStaticFileHandlerDefaultMaxAgeTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest, HttpClientGet128kbResponseTest, HttpClientGetResponseBodyTest, HttpClientGetNoResponseBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeat, HttpClientPostRepeat) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest) } const wwwRootPath = "/tmp/www_root" const defaultHttpTimeout = time.Second * 10 func httpDownloadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data interface{}) { url, isValid := data.(string) s.AssertEqual(true, isValid) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", url, nil) s.AssertNil(err, fmt.Sprint(err)) t := time.Now() resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.AssertHttpStatus(resp, 200) _, err = io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) duration := time.Since(t) experiment.RecordValue("Download Speed", (float64(resp.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2)) } func HttpGetTpsInterruptModeTest(s *NoTopoSuite) { HttpGetTpsTest(s) } func HttpGetTpsTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() url := "http://" + serverAddress + ":8080/test_file_10M" vpp.Vppctl("http tps uri tcp://0.0.0.0/8080") s.RunBenchmark("HTTP tps download 10M", 10, 0, httpDownloadBenchmark, url) } func httpUploadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data interface{}) { url, isValid := data.(string) s.AssertEqual(true, isValid) body := make([]byte, 10485760) _, err := rand.Read(body) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) s.AssertNil(err, fmt.Sprint(err)) t := time.Now() resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.AssertHttpStatus(resp, 200) _, err = io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) duration := time.Since(t) experiment.RecordValue("Upload Speed", (float64(req.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2)) } func HttpPostTpsInterruptModeTest(s *NoTopoSuite) { HttpPostTpsTest(s) } func HttpPostTpsTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() url := "http://" + serverAddress + ":8080/test_file_10M" vpp.Vppctl("http tps uri tcp://0.0.0.0/8080") s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url) } func HttpPersistentConnectionTest(s *NoTopoSuite) { // testing url handler app do not support multi-thread s.SkipIfMultiWorker() vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) s.Log(vpp.Vppctl("test-url-handler enable")) transport := http.DefaultTransport transport.(*http.Transport).Proxy = nil transport.(*http.Transport).DisableKeepAlives = false client := &http.Client{ Transport: transport, Timeout: time.Second * 30, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }} body := []byte("{\"sandwich\": {\"spam\": 2, \"eggs\": 1}}") req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test3", bytes.NewBuffer(body)) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) s.AssertEqual(false, resp.Close) s.AssertHttpContentLength(resp, int64(0)) o1 := vpp.Vppctl("show session verbose proto http state ready") s.Log(o1) s.AssertContains(o1, "established") req, err = http.NewRequest("GET", "http://"+serverAddress+":80/test1", nil) s.AssertNil(err, fmt.Sprint(err)) clientTrace := &httptrace.ClientTrace{ GotConn: func(info httptrace.GotConnInfo) { s.AssertEqual(true, info.Reused, "connection not reused") }, } req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientTrace)) resp, err = client.Do(req) s.AssertNil(err, fmt.Sprint(err)) s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) s.AssertEqual(false, resp.Close) s.AssertHttpBody(resp, "hello") o2 := vpp.Vppctl("show session verbose proto http state ready") s.Log(o2) s.AssertContains(o2, "established") s.AssertEqual(o1, o2) req, err = http.NewRequest("GET", "http://"+serverAddress+":80/test2", nil) s.AssertNil(err, fmt.Sprint(err)) req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientTrace)) resp, err = client.Do(req) s.AssertNil(err, fmt.Sprint(err)) s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) s.AssertEqual(false, resp.Close) s.AssertHttpBody(resp, "some data") o2 = vpp.Vppctl("show session verbose proto http state ready") s.Log(o2) s.AssertContains(o2, "established") s.AssertEqual(o1, o2) } func HttpPipeliningTest(s *NoTopoSuite) { // testing url handler app do not support multi-thread s.SkipIfMultiWorker() vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) s.Log(vpp.Vppctl("test-url-handler enable")) req1 := "GET /test_delayed HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n" req2 := "GET /test1 HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n" conn, err := net.DialTimeout("tcp", serverAddress+":80", time.Second*30) s.AssertNil(err, fmt.Sprint(err)) defer conn.Close() err = conn.SetDeadline(time.Now().Add(time.Second * 15)) s.AssertNil(err, fmt.Sprint(err)) n, err := conn.Write([]byte(req1)) s.AssertNil(err, fmt.Sprint(err)) s.AssertEqual(n, len([]rune(req1))) // send second request a bit later so first is already in progress time.Sleep(500 * time.Millisecond) n, err = conn.Write([]byte(req2)) s.AssertNil(err, fmt.Sprint(err)) s.AssertEqual(n, len([]rune(req2))) reply := make([]byte, 1024) _, err = conn.Read(reply) s.AssertNil(err, fmt.Sprint(err)) s.Log(string(reply)) s.AssertContains(string(reply), "delayed data", "first request response not received") s.AssertNotContains(string(reply), "hello", "second request response received") // make sure response for second request is not received later _, err = conn.Read(reply) s.AssertMatchError(err, os.ErrDeadlineExceeded, "second request response received") } func HttpCliTest(s *VethsSuite) { serverContainer := s.GetContainerByName("server-vpp") clientContainer := s.GetContainerByName("client-vpp") serverVeth := s.GetInterfaceByName(ServerInterfaceName) serverContainer.VppInstance.Vppctl("http cli server") uri := "http://" + serverVeth.Ip4AddressString() + "/80" o := clientContainer.VppInstance.Vppctl("http cli client" + " uri " + uri + " query /show/vlib/graph") s.Log(o) s.AssertContains(o, "", " not found in the result!") s.AssertContains(o, "", " not found in the result!") } func HttpCliConnectErrorTest(s *VethsSuite) { clientContainer := s.GetContainerByName("client-vpp") serverVeth := s.GetInterfaceByName(ServerInterfaceName) uri := "http://" + serverVeth.Ip4AddressString() + "/80" o := clientContainer.VppInstance.Vppctl("http cli client" + " uri " + uri + " query /show/vlib/graph") s.Log(o) s.AssertContains(o, "failed to connect") } func HttpClientTest(s *NoTopoSuite) { serverAddress := s.HostAddr() server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress+":80") s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( ghttp.CombineHandlers( s.LogHttpReq(true), ghttp.VerifyRequest("GET", "/test"), ghttp.VerifyHeader(http.Header{"User-Agent": []string{"http_cli_client"}}), ghttp.VerifyHeader(http.Header{"Accept": []string{"text/html"}}), ghttp.RespondWith(http.StatusOK, "
Hello
"), )) server.Start() defer server.Close() uri := "http://" + serverAddress + "/80" vpp := s.GetContainerByName("vpp").VppInstance o := vpp.Vppctl("http cli client uri " + uri + " query /test") s.Log(o) s.AssertContains(o, "", " not found in the result!") s.AssertContains(o, "", " not found in the result!") } func HttpClientErrRespTest(s *NoTopoSuite) { serverAddress := s.HostAddr() server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress+":80") s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( ghttp.CombineHandlers( s.LogHttpReq(true), ghttp.VerifyRequest("GET", "/test"), ghttp.RespondWith(http.StatusNotFound, "404: Not Found"), )) server.Start() defer server.Close() uri := "http://" + serverAddress + "/80" vpp := s.GetContainerByName("vpp").VppInstance o := vpp.Vppctl("http cli client uri " + uri + " query /test") s.Log(o) s.AssertContains(o, "404: Not Found", "error not found in the result!") } func HttpClientPostFormTest(s *NoTopoSuite) { serverAddress := s.HostAddr() body := "field1=value1&field2=value2" server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress+":80") s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( ghttp.CombineHandlers( s.LogHttpReq(true), ghttp.VerifyRequest("POST", "/test"), ghttp.VerifyContentType("application/x-www-form-urlencoded"), ghttp.VerifyHeaderKV("Hello", "World"), ghttp.VerifyBody([]byte(body)), ghttp.RespondWith(http.StatusOK, nil), )) server.Start() defer server.Close() uri := "http://" + serverAddress + "/80" vpp := s.GetContainerByName("vpp").VppInstance o := vpp.Vppctl("http client post verbose header Hello:World uri " + uri + " target /test data " + body) s.Log(o) s.AssertContains(o, "200 OK") } func HttpClientGetResponseBodyTest(s *NoTopoSuite) { response := "hello world" size := len(response) httpClientGet(s, response, size) } func HttpClientGet128kbResponseTest(s *NoTopoSuite) { response := strings.Repeat("a", 128*1024) size := len(response) httpClientGet(s, response, size) } func HttpClientGetNoResponseBodyTest(s *NoTopoSuite) { response := "" httpClientGet(s, response, 0) } func httpClientGet(s *NoTopoSuite, response string, size int) { serverAddress := s.HostAddr() vpp := s.GetContainerByName("vpp").VppInstance server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress+":80") s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( ghttp.CombineHandlers( s.LogHttpReq(false), ghttp.VerifyRequest("GET", "/test"), ghttp.VerifyHeaderKV("Hello", "World"), ghttp.VerifyHeaderKV("Test-H2", "Test-K2"), ghttp.RespondWith(http.StatusOK, string(response), http.Header{"Content-Length": {strconv.Itoa(size)}}), )) server.Start() defer server.Close() uri := "http://" + serverAddress + "/80" cmd := "http client use-ptr verbose header Hello:World header Test-H2:Test-K2 save-to response.txt uri " + uri + " target /test" o := vpp.Vppctl(cmd) outputLen := len(o) if outputLen > 500 { s.Log(o[:500]) s.Log("* HST Framework: output limited to 500 chars to avoid flooding the console. Output length: " + fmt.Sprint(outputLen)) } else { s.Log(o) } s.AssertContains(o, "200 OK") s.AssertContains(o, response) s.AssertContains(o, "Content-Length: "+strconv.Itoa(size)) file_contents := vpp.Container.Exec(false, "cat /tmp/response.txt") s.AssertContains(file_contents, response) } func startSimpleServer(s *NoTopoSuite, replyCount *int, serverAddress string) (server *httptest.Server) { var err error server = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello") *replyCount++ })) server.Listener, err = net.Listen("tcp", serverAddress+":80") s.AssertNil(err, "Error while creating listener.") server.Start() return server } func HttpClientGetRepeat(s *NoTopoSuite) { httpClientRepeat(s, "") } func HttpClientPostRepeat(s *NoTopoSuite) { httpClientRepeat(s, "post") } func httpClientRepeat(s *NoTopoSuite, requestMethod string) { replyCount := 0 vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.HostAddr() repeatAmount := 10000 server := startSimpleServer(s, &replyCount, serverAddress) defer server.Close() if requestMethod == "post" { fileName := "/tmp/test_file.txt" s.Log(vpp.Container.Exec(false, "fallocate -l 64 "+fileName)) s.Log(vpp.Container.Exec(false, "ls -la "+fileName)) requestMethod += " file /tmp/test_file.txt" } uri := "http://" + serverAddress + "/80" cmd := fmt.Sprintf("http client %s use-ptr duration 10 header Hello:World uri %s target /index.html", requestMethod, uri) s.Log("Duration 10s") o := vpp.Vppctl(cmd) outputLen := len(o) if outputLen > 500 { s.Log(o[:500]) s.Log("* HST Framework: output limited to 500 chars to avoid flooding the console. Output length: " + fmt.Sprint(outputLen)) } else { s.Log(o) } s.Log("Server response count: %d", replyCount) s.AssertNotNil(o) s.AssertNotContains(o, "error") s.AssertGreaterThan(replyCount, 15000) cmd = fmt.Sprintf("http client %s use-ptr repeat %d header Hello:World uri %s target /index.html", requestMethod, repeatAmount, uri) replyCount = 0 s.Log("Repeat %d", repeatAmount) o = vpp.Vppctl(cmd) outputLen = len(o) if outputLen > 500 { s.Log(o[:500]) s.Log("* HST Framework: output limited to 500 chars to avoid flooding the console. Output length: " + fmt.Sprint(outputLen)) } else { s.Log(o) } s.Log("Server response count: %d", replyCount) s.AssertNotNil(o) s.AssertNotContains(o, "error") s.AssertEqual(repeatAmount, replyCount) } func HttpClientGetTimeout(s *NoTopoSuite) { serverAddress := s.HostAddr() vpp := s.GetContainerByName("vpp").VppInstance server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress+":"+s.GetPortFromPpid()) s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( ghttp.CombineHandlers( s.LogHttpReq(false), ghttp.VerifyRequest("GET", "/timeout"), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(5 * time.Second) }), ghttp.RespondWith(http.StatusOK, nil), )) server.Start() defer server.Close() uri := "http://" + serverAddress + "/" + s.GetPortFromPpid() cmd := "http client verbose timeout 1 uri " + uri + " target /timeout" o := vpp.Vppctl(cmd) s.Log(o) s.AssertContains(o, "error: timeout") } func httpClientPostFile(s *NoTopoSuite, usePtr bool, fileSize int) { serverAddress := s.HostAddr() vpp := s.GetContainerByName("vpp").VppInstance fileName := "/tmp/test_file.txt" s.Log(vpp.Container.Exec(false, "fallocate -l "+strconv.Itoa(fileSize)+" "+fileName)) s.Log(vpp.Container.Exec(false, "ls -la "+fileName)) server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress+":80") s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( ghttp.CombineHandlers( s.LogHttpReq(false), ghttp.VerifyRequest("POST", "/test"), ghttp.VerifyHeader(http.Header{"Content-Length": []string{strconv.Itoa(fileSize)}}), ghttp.VerifyContentType("application/octet-stream"), ghttp.RespondWith(http.StatusOK, nil), )) server.Start() defer server.Close() uri := "http://" + serverAddress + "/80" cmd := "http client post verbose uri " + uri + " target /test file " + fileName if usePtr { cmd += " use-ptr" } o := vpp.Vppctl(cmd) s.Log(o) s.AssertContains(o, "200 OK") } func HttpClientPostFileTest(s *NoTopoSuite) { httpClientPostFile(s, false, 32768) } func HttpClientPostFilePtrTest(s *NoTopoSuite) { httpClientPostFile(s, true, 131072) } func HttpUnitTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance o := vpp.Vppctl("test http all") s.Log(o) s.AssertNotContains(o, "FAIL") } func HttpStaticPromTest(s *NoTopoSuite) { query := "stats.prom" vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 5) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/"+query, nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, false)) s.AssertHttpStatus(resp, 200) s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/plain") s.AssertGreaterThan(resp.ContentLength, 0) _, err = io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) } func promReq(s *NoTopoSuite, url string, timeout time.Duration) { client := NewHttpClient(timeout) req, err := http.NewRequest("GET", url, nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.AssertHttpStatus(resp, 200) _, err = io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) } func promReqWg(s *NoTopoSuite, url string, wg *sync.WaitGroup) { defer GinkgoRecover() defer wg.Done() promReq(s, url, defaultHttpTimeout) } func PromConcurrentConnectionsTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() url := "http://" + serverAddress + ":80/stats.prom" s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 5) var wg sync.WaitGroup for i := 0; i < 20; i++ { wg.Add(1) go promReqWg(s, url, &wg) } wg.Wait() s.Log(vpp.Vppctl("show session verbose proto http")) } func PromConsecutiveConnectionsTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() url := "http://" + serverAddress + ":80/stats.prom" s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 5) for i := 0; i < 1000; i++ { promReq(s, url, time.Millisecond*500) } } func PromMemLeakTest(s *NoTopoSuite) { s.SkipUnlessLeakCheck() vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() url := "http://" + serverAddress + ":80/stats.prom" /* no goVPP less noise */ vpp.Disconnect() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 3) /* warmup request (FIB) */ promReq(s, url, defaultHttpTimeout) vpp.EnableMemoryTrace() traces1, err := vpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) /* collect stats couple of times */ for i := 0; i < 5; i++ { time.Sleep(time.Second * 1) promReq(s, url, defaultHttpTimeout) } /* let's give it some time to clean up sessions */ time.Sleep(time.Second * 5) traces2, err := vpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) vpp.MemLeakCheck(traces1, traces2) } func HttpClientGetMemLeakTest(s *VethsSuite) { s.SkipUnlessLeakCheck() serverContainer := s.GetContainerByName("server-vpp").VppInstance clientContainer := s.GetContainerByName("client-vpp").VppInstance serverVeth := s.GetInterfaceByName(ServerInterfaceName) /* no goVPP less noise */ clientContainer.Disconnect() serverContainer.Vppctl("http cli server") uri := "http://" + serverVeth.Ip4AddressString() + "/80" /* warmup request (FIB) */ clientContainer.Vppctl("http cli client uri " + uri + " query /show/version") /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */ time.Sleep(time.Second * 12) clientContainer.EnableMemoryTrace() traces1, err := clientContainer.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) clientContainer.Vppctl("http cli client uri " + uri + " query /show/vlib/graph") /* let's give it some time to clean up sessions */ time.Sleep(time.Second * 12) traces2, err := clientContainer.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) clientContainer.MemLeakCheck(traces1, traces2) } func HttpClientPostMemLeakTest(s *NoTopoSuite) { s.SkipUnlessLeakCheck() serverAddress := s.HostAddr() body := "field1=value1&field2=value2" uri := "http://" + serverAddress + "/80" vpp := s.GetContainerByName("vpp").VppInstance /* no goVPP less noise */ vpp.Disconnect() server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress+":80") s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( ghttp.CombineHandlers( ghttp.VerifyRequest("POST", "/test"), ghttp.RespondWith(http.StatusOK, nil), ), ghttp.CombineHandlers( ghttp.VerifyRequest("POST", "/test"), ghttp.RespondWith(http.StatusOK, nil), ), ) server.Start() defer server.Close() /* warmup request (FIB) */ vpp.Vppctl("http post uri " + uri + " target /test data " + body) /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */ time.Sleep(time.Second * 12) vpp.EnableMemoryTrace() traces1, err := vpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) vpp.Vppctl("http post uri " + uri + " target /test data " + body) /* let's give it some time to clean up sessions */ time.Sleep(time.Second * 12) traces2, err := vpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) vpp.MemLeakCheck(traces1, traces2) } func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) { s.SkipUnlessLeakCheck() vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() /* no goVPP less noise */ vpp.Disconnect() vpp.Vppctl("http cli server") /* warmup request (FIB) */ _, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n") s.AssertNil(err, fmt.Sprint(err)) /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */ time.Sleep(time.Second * 12) vpp.EnableMemoryTrace() traces1, err := vpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) _, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n") s.AssertNil(err, fmt.Sprint(err)) /* let's give it some time to clean up sessions */ time.Sleep(time.Second * 12) traces2, err := vpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) vpp.MemLeakCheck(traces1, traces2) } func runWrkPerf(s *NoTopoSuite) { nConnections := 1000 serverAddress := s.VppAddr() wrkCont := s.GetContainerByName("wrk") args := fmt.Sprintf("-c %d -t 2 -d 30s http://%s:80/64B", nConnections, serverAddress) wrkCont.ExtraRunningArgs = args wrkCont.Run() s.Log("Please wait for 30s, test is running.") o, err := wrkCont.GetOutput() s.Log(o) s.AssertEmpty(err, "err: '%s'", err) } func HttpStaticFileHandlerWrkTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) content := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" err := vpp.Container.CreateFile(wwwRootPath+"/64B", content) s.AssertNil(err, fmt.Sprint(err)) s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 private-segment-size 256m")) runWrkPerf(s) } func HttpStaticUrlHandlerWrkTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers private-segment-size 256m")) s.Log(vpp.Vppctl("test-url-handler enable")) runWrkPerf(s) } func HttpStaticFileHandlerDefaultMaxAgeTest(s *NoTopoSuite) { HttpStaticFileHandlerTestFunction(s, "default") } func HttpStaticFileHandlerTest(s *NoTopoSuite) { HttpStaticFileHandlerTestFunction(s, "123") } func HttpStaticFileHandlerTestFunction(s *NoTopoSuite, max_age string) { var maxAgeFormatted string if max_age == "default" { maxAgeFormatted = "" max_age = "600" } else { maxAgeFormatted = "max-age " + max_age } content := "Hello
" content2 := "Page
" vpp := s.GetContainerByName("vpp").VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) err := vpp.Container.CreateFile(wwwRootPath+"/index.html", content) s.AssertNil(err, fmt.Sprint(err)) err = vpp.Container.CreateFile(wwwRootPath+"/page.html", content2) s.AssertNil(err, fmt.Sprint(err)) serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m " + maxAgeFormatted)) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/index.html", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html") s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age) parsedTime, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified")) s.AssertNil(err, fmt.Sprint(err)) s.AssertTimeEqualWithinThreshold(parsedTime, time.Now(), time.Minute*5) s.AssertEqual(len(resp.Header.Get("Last-Modified")), 29) s.AssertHttpContentLength(resp, int64(len([]rune(content)))) s.AssertHttpBody(resp, content) o := vpp.Vppctl("show http static server cache verbose") s.Log(o) s.AssertContains(o, "index.html") s.AssertNotContains(o, "page.html") resp, err = client.Do(req) s.AssertNil(err, fmt.Sprint(err)) s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html") s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age) s.AssertHttpContentLength(resp, int64(len([]rune(content)))) s.AssertHttpBody(resp, content) req, err = http.NewRequest("GET", "http://"+serverAddress+":80/page.html", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err = client.Do(req) s.AssertNil(err, fmt.Sprint(err)) s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html") s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age) s.AssertHttpContentLength(resp, int64(len([]rune(content2)))) s.AssertHttpBody(resp, content2) o = vpp.Vppctl("show http static server cache verbose") s.Log(o) s.AssertContains(o, "index.html") s.AssertContains(o, "page.html") } func HttpStaticPathTraversalTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) vpp.Container.Exec(false, "mkdir -p "+"/tmp/secret_folder") err := vpp.Container.CreateFile("/tmp/secret_folder/secret_file.txt", "secret") s.AssertNil(err, fmt.Sprint(err)) serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug")) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/../secret_folder/secret_file.txt", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 404) s.AssertHttpHeaderNotPresent(resp, "Content-Type") s.AssertHttpHeaderNotPresent(resp, "Cache-Control") s.AssertHttpContentLength(resp, int64(0)) } func HttpStaticMovedTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath+"/tmp.aaa") err := vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "Hello
") s.AssertNil(err, fmt.Sprint(err)) serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug")) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/tmp.aaa", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 301) s.AssertHttpHeaderWithValue(resp, "Location", "http://"+serverAddress+"/tmp.aaa/index.html") s.AssertHttpHeaderNotPresent(resp, "Content-Type") s.AssertHttpHeaderNotPresent(resp, "Cache-Control") s.AssertHttpContentLength(resp, int64(0)) } func HttpStaticNotFoundTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug")) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/notfound.html", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 404) s.AssertHttpHeaderNotPresent(resp, "Content-Type") s.AssertHttpHeaderNotPresent(resp, "Cache-Control") s.AssertHttpContentLength(resp, int64(0)) } func HttpCliMethodNotAllowedTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 405) s.AssertHttpHeaderWithValue(resp, "Allow", "GET", "server MUST generate an Allow header") s.AssertHttpHeaderNotPresent(resp, "Content-Type") s.AssertHttpContentLength(resp, int64(0)) } func HttpCliBadRequestTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 400) s.AssertHttpHeaderNotPresent(resp, "Content-Type") s.AssertHttpContentLength(resp, int64(0)) } func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/80 url-handlers debug")) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "https://"+serverAddress+":80/version.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) data, err := io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(string(data), "vpp_details") s.AssertContains(string(data), "version") s.AssertContains(string(data), "build_date") s.AssertNotContains(string(data), "build_by") s.AssertNotContains(string(data), "build_host") s.AssertNotContains(string(data), "build_dir") s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/version.json?verbose=true", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) data, err := io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(string(data), "vpp_details") s.AssertContains(string(data), "version") s.AssertContains(string(data), "build_date") s.AssertContains(string(data), "build_by") s.AssertContains(string(data), "build_host") s.AssertContains(string(data), "build_dir") s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/interface_list.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) data, err := io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(string(data), "interface_list") s.AssertContains(string(data), s.VppIfName()) s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/interface_stats.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) data, err := io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(string(data), "interface_stats") s.AssertContains(string(data), "local0") s.AssertContains(string(data), s.VppIfName()) s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } func validatePostInterfaceStats(s *NoTopoSuite, data string) { s.AssertContains(data, "interface_stats") s.AssertContains(data, s.VppIfName()) s.AssertNotContains(data, "error") s.AssertNotContains(data, "local0") } func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) body := []byte(s.VppIfName()) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("POST", "http://"+serverAddress+":80/interface_stats.json", bytes.NewBuffer(body)) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) data, err := io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) validatePostInterfaceStats(s, string(data)) s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } func HttpStaticMacTimeTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) s.Log(vpp.Vppctl("mactime enable-disable " + s.VppIfName())) client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/mactime.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) data, err := io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(string(data), "mactime") s.AssertContains(string(data), s.HostAddr()) s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).HwAddress.String()) s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") parsedTime, err := time.Parse(time.RFC1123, resp.Header.Get("Date")) s.AssertNil(err, fmt.Sprint(err)) s.AssertTimeEqualWithinThreshold(parsedTime, time.Now(), time.Minute*5) s.AssertEqual(len(resp.Header.Get("Date")), 29) } func HttpInvalidRequestLineTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") resp, err := TcpSendReceive(serverAddress+":80", " GET / HTTP/1.1") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed") resp, err = TcpSendReceive(serverAddress+":80", "\rGET / HTTP/1.1") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed") resp, err = TcpSendReceive(serverAddress+":80", "\nGET / HTTP/1.1") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid framing not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid framing not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET /\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "HTTP-version must be present") resp, err = TcpSendReceive(serverAddress+":80", "GET HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "request-target must be present") resp, err = TcpSendReceive(serverAddress+":80", "GET HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "request-target must be present") resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/x\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP/x' invalid http version not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP1.1' invalid http version not allowed") } func HttpRequestLineTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") resp, err := TcpSendReceive(serverAddress+":80", "\r\nGET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 200 OK") s.AssertContains(resp, "", "html content not found") } func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) resp, err := TcpSendReceive(serverAddress+":80", "GET /interface|stats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'|' not allowed in target path") resp, err = TcpSendReceive(serverAddress+":80", "GET /interface#stats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'#' not allowed in target path") resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%stats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%1stats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%Bstats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%stats.json%B HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose>true HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'>' not allowed in target query") resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose%true HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target query") resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose=%1 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target query") } func HttpInvalidContentLengthTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") resp, err := TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length:\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value must be present") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length: \r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value must be present") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length: a\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value other than digit not allowed") } func HttpContentLengthTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) ifName := s.VppIfName() resp, err := TcpSendReceive(serverAddress+":80", "POST /interface_stats.json HTTP/1.1\r\nContent-Length:4\r\n\r\n"+ifName) s.AssertNil(err, fmt.Sprint(err)) validatePostInterfaceStats(s, resp) resp, err = TcpSendReceive(serverAddress+":80", "POST /interface_stats.json HTTP/1.1\r\n Content-Length: 4 \r\n\r\n"+ifName) s.AssertNil(err, fmt.Sprint(err)) validatePostInterfaceStats(s, resp) resp, err = TcpSendReceive(serverAddress+":80", "POST /interface_stats.json HTTP/1.1\r\n\tContent-Length:\t\t4\r\n\r\n"+ifName) s.AssertNil(err, fmt.Sprint(err)) validatePostInterfaceStats(s, resp) } func HttpMethodNotImplementedTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("OPTIONS", "http://"+serverAddress+":80/show/version", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 501) s.AssertHttpHeaderNotPresent(resp, "Content-Type") s.AssertHttpContentLength(resp, int64(0)) } func HttpVersionNotSupportedTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") resp, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/2\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 505 HTTP Version Not Supported") } func HttpUriDecodeTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/sh%6fw%20versio%6E%20verbose", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) data, err := io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) s.AssertNotContains(string(data), "unknown input") s.AssertContains(string(data), "Compiler") s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html") } func HttpHeadersTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") resp, err := TcpSendReceive( serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:test\r\nAccept:text/xml\r\nAccept:\ttext/plain\t \r\nAccept:text/html\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 200 OK") s.AssertContains(resp, "Content-Type: text/plain") s.AssertNotContains(resp, "", "html content received instead of plain text") } func HttpInvalidHeadersTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") resp, err := TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nUser-Agent: test\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Header section must end with CRLF CRLF") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser@Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'@' not allowed in field name") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "incomplete field line not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\n: test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field name not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\rUser-Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid field line end not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\nUser-Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid field line end not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed") resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent: \r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed") } func HeaderServerTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() vpp.Vppctl("http cli server") client := NewHttpClient(defaultHttpTimeout) req, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 200) s.AssertHttpHeaderWithValue(resp, "Server", "http_cli_server") s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html") } func HttpConnTimeoutTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance serverAddress := s.VppAddr() s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug keepalive-timeout 2")) req := "GET /version.json HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n" conn, err := net.DialTimeout("tcp", serverAddress+":80", time.Second*30) s.AssertNil(err, fmt.Sprint(err)) defer conn.Close() err = conn.SetDeadline(time.Now().Add(time.Second * 30)) s.AssertNil(err, fmt.Sprint(err)) _, err = conn.Write([]byte(req)) s.AssertNil(err, fmt.Sprint(err)) reply := make([]byte, 1024) _, err = conn.Read(reply) s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(string(reply), "HTTP/1.1 200 OK") s.Log(vpp.Vppctl("show session verbose 2")) s.Log("waiting for close on the server side") time.Sleep(time.Second * 5) s.Log(vpp.Vppctl("show session verbose 2")) _, err = conn.Write([]byte(req)) s.AssertNil(err, fmt.Sprint(err)) reply = make([]byte, 1024) _, err = conn.Read(reply) s.AssertMatchError(err, io.EOF, "connection not closed by server") }