diff options
author | Matus Fabian <matfabia@cisco.com> | 2024-12-10 16:15:06 +0100 |
---|---|---|
committer | Florin Coras <florin.coras@gmail.com> | 2025-01-09 10:59:16 +0000 |
commit | c19cca931af3baf847d66bce9e5dd660861fc92e (patch) | |
tree | a36f0ba9f31c379984a3de4dfb54baa8808f5bbe /extras | |
parent | e89c97a3ee1538f3693fcd298831952d5df19ec2 (diff) |
hsa: proxying UDP in HTTP/1.1
Type: feature
Change-Id: Ic0ff9b9bfbad9fbc602fbcec0d8906cd21d63a2c
Signed-off-by: Matus Fabian <matfabia@cisco.com>
Diffstat (limited to 'extras')
-rw-r--r-- | extras/hs-test/go.mod | 13 | ||||
-rw-r--r-- | extras/hs-test/go.sum | 26 | ||||
-rw-r--r-- | extras/hs-test/infra/connect_udp_client.go | 147 | ||||
-rw-r--r-- | extras/hs-test/infra/suite_vpp_udp_proxy.go | 14 | ||||
-rw-r--r-- | extras/hs-test/infra/utils.go | 1 | ||||
-rw-r--r-- | extras/hs-test/proxy_test.go | 262 |
6 files changed, 446 insertions, 17 deletions
diff --git a/extras/hs-test/go.mod b/extras/hs-test/go.mod index 01cb9000bdc..4cc24d510cc 100644 --- a/extras/hs-test/go.mod +++ b/extras/hs-test/go.mod @@ -9,6 +9,7 @@ require ( github.com/edwarnicke/exechelper v1.0.3 github.com/onsi/ginkgo/v2 v2.17.2 github.com/onsi/gomega v1.33.1 + github.com/quic-go/quic-go v0.48.2 github.com/sirupsen/logrus v1.9.3 go.fd.io/govpp v0.10.0 gopkg.in/yaml.v3 v3.0.1 @@ -39,6 +40,7 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect @@ -50,11 +52,14 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/sdk v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.uber.org/mock v0.4.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/extras/hs-test/go.sum b/extras/hs-test/go.sum index fb555ad7abf..19a120b3007 100644 --- a/extras/hs-test/go.sum +++ b/extras/hs-test/go.sum @@ -127,6 +127,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -193,6 +197,8 @@ go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+ go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= @@ -200,32 +206,36 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/W golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/extras/hs-test/infra/connect_udp_client.go b/extras/hs-test/infra/connect_udp_client.go new file mode 100644 index 00000000000..595f7e7f2e6 --- /dev/null +++ b/extras/hs-test/infra/connect_udp_client.go @@ -0,0 +1,147 @@ +package hst + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "time" + + "github.com/quic-go/quic-go/http3" + "github.com/quic-go/quic-go/quicvarint" +) + +type CapsuleParseError struct { + Err error +} + +func (e *CapsuleParseError) Error() string { + return e.Err.Error() +} + +type ConnectUdpClient struct { + log bool + suite *HstSuite + timeout time.Duration + Conn net.Conn +} + +func (s *HstSuite) NewConnectUdpClient(timeout time.Duration, log bool) *ConnectUdpClient { + client := &ConnectUdpClient{log: log, suite: s, timeout: timeout} + return client +} + +func writeConnectUdpReq(target string) []byte { + var b bytes.Buffer + + fmt.Fprintf(&b, "GET %s HTTP/1.1\r\n", target) + u, _ := url.Parse(target) + fmt.Fprintf(&b, "Host: %s\r\n", u.Host) + fmt.Fprintf(&b, "User-Agent: hs-test\r\n") + fmt.Fprintf(&b, "Connection: Upgrade\r\n") + fmt.Fprintf(&b, "Upgrade: connect-udp\r\n") + fmt.Fprintf(&b, "Capsule-Protocol: ?1\r\n") + io.WriteString(&b, "\r\n") + + return b.Bytes() +} + +func (c *ConnectUdpClient) Dial(proxyAddress, targetUri string) error { + req := writeConnectUdpReq(targetUri) + conn, err := net.DialTimeout("tcp", proxyAddress, c.timeout) + if err != nil { + return err + } + + if c.log { + c.suite.Log("* Connected to proxy") + } + + conn.SetDeadline(time.Now().Add(time.Second * c.timeout)) + _, err = conn.Write(req) + if err != nil { + return err + } + + r := bufio.NewReader(conn) + resp, err := http.ReadResponse(r, nil) + if err != nil { + return err + } + + if c.log { + c.suite.Log(DumpHttpResp(resp, true)) + } + + if resp.StatusCode != http.StatusSwitchingProtocols { + return errors.New("request failed: " + resp.Status) + } + if resp.Header.Get("Connection") != "upgrade" || resp.Header.Get("Upgrade") != "connect-udp" || resp.Header.Get("Capsule-Protocol") != "?1" { + return errors.New("invalid response") + } + + if c.log { + c.suite.Log("* CONNECT-UDP tunnel established") + } + c.Conn = conn + return nil +} + +func (c *ConnectUdpClient) Close() error { + return c.Conn.Close() +} + +func (c *ConnectUdpClient) WriteCapsule(capsuleType http3.CapsuleType, payload []byte) error { + err := c.Conn.SetWriteDeadline(time.Now().Add(c.timeout)) + if err != nil { + return err + } + var buf bytes.Buffer + err = http3.WriteCapsule(&buf, capsuleType, payload) + if err != nil { + return err + } + _, err = c.Conn.Write(buf.Bytes()) + if err != nil { + return err + } + return nil +} + +func (c *ConnectUdpClient) WriteDgramCapsule(payload []byte) error { + b := make([]byte, 0) + b = quicvarint.Append(b, 0) + b = append(b, payload...) + return c.WriteCapsule(0, b) +} + +func (c *ConnectUdpClient) ReadDgramCapsule() ([]byte, error) { + err := c.Conn.SetReadDeadline(time.Now().Add(c.timeout)) + if err != nil { + return nil, err + } + r := bufio.NewReader(c.Conn) + capsuleType, payloadReader, err := http3.ParseCapsule(r) + if err != nil { + return nil, err + } + if capsuleType != 0 { + return nil, &CapsuleParseError{errors.New("capsule type should be 0")} + } + b := make([]byte, 1024) + n, err := payloadReader.Read(b) + if err != nil { + return nil, err + } + if n < 3 { + return nil, &CapsuleParseError{errors.New("response payload too short")} + } + if b[0] != 0 { + return nil, &CapsuleParseError{errors.New("context id should be 0")} + } + return b[1:n], nil +} diff --git a/extras/hs-test/infra/suite_vpp_udp_proxy.go b/extras/hs-test/infra/suite_vpp_udp_proxy.go index 2290aeec6a2..62bf3ddd466 100644 --- a/extras/hs-test/infra/suite_vpp_udp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_udp_proxy.go @@ -5,6 +5,7 @@ import ( "net" "reflect" "runtime" + "strconv" "strings" "time" @@ -15,6 +16,7 @@ type VppUdpProxySuite struct { HstSuite proxyPort int serverPort int + MaxTimeout time.Duration Interfaces struct { Client *NetInterface Server *NetInterface @@ -42,13 +44,21 @@ func (s *VppUdpProxySuite) SetupSuite() { s.Interfaces.Client = s.GetInterfaceByName("hstcln") s.Interfaces.Server = s.GetInterfaceByName("hstsrv") s.Containers.VppProxy = s.GetContainerByName("vpp") + + if *IsVppDebug { + s.MaxTimeout = time.Second * 600 + } else { + s.MaxTimeout = time.Second * 2 + } } func (s *VppUdpProxySuite) SetupTest() { s.HstSuite.SetupTest() // VPP proxy - vpp, err := s.Containers.VppProxy.newVppInstance(s.Containers.VppProxy.AllocatedCpus) + var memoryConfig Stanza + memoryConfig.NewStanza("memory").Append("main-heap-size 2G") + vpp, err := s.Containers.VppProxy.newVppInstance(s.Containers.VppProxy.AllocatedCpus, memoryConfig) s.AssertNotNil(vpp, fmt.Sprint(err)) s.AssertNil(vpp.Start()) @@ -119,7 +129,7 @@ func (s *VppUdpProxySuite) StartEchoServer() *net.UDPConn { } } }() - s.Log("started") + s.Log("* started udp echo server " + s.ServerAddr() + ":" + strconv.Itoa(s.ServerPort())) return conn } diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go index b690efc32ca..bd603f863fc 100644 --- a/extras/hs-test/infra/utils.go +++ b/extras/hs-test/infra/utils.go @@ -17,6 +17,7 @@ import ( const networkTopologyDir string = "topo-network/" const containerTopologyDir string = "topo-containers/" +const HttpCapsuleTypeDatagram = uint64(0) type Stanza struct { content string diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index d371de46cbb..ec90d24ba36 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "strconv" + "strings" "sync" "sync/atomic" "time" @@ -23,9 +24,10 @@ func init() { RegisterVppProxyTests(VppProxyHttpGetTcpTest, VppProxyHttpGetTlsTest, VppProxyHttpPutTcpTest, VppProxyHttpPutTlsTest, VppConnectProxyGetTest, VppConnectProxyPutTest) RegisterVppProxySoloTests(VppProxyHttpGetTcpMTTest, VppProxyHttpPutTcpMTTest, VppProxyTcpIperfMTTest, - VppProxyUdpIperfMTTest, VppConnectProxyStressTest, VppConnectProxyStressMTTest) - RegisterVppUdpProxyTests(VppProxyUdpTest) - RegisterVppUdpProxySoloTests(VppProxyUdpMigrationMTTest) + VppProxyUdpIperfMTTest, VppConnectProxyStressTest, VppConnectProxyStressMTTest, VppConnectProxyConnectionFailedMTTest) + RegisterVppUdpProxyTests(VppProxyUdpTest, VppConnectUdpProxyTest, VppConnectUdpInvalidCapsuleTest, + VppConnectUdpUnknownCapsuleTest, VppConnectUdpClientCloseTest) + RegisterVppUdpProxySoloTests(VppProxyUdpMigrationMTTest, VppConnectUdpStressMTTest, VppConnectUdpStressTest) RegisterEnvoyProxyTests(EnvoyProxyHttpGetTcpTest, EnvoyProxyHttpPutTcpTest) RegisterNginxProxyTests(NginxMirroringTest) RegisterNginxProxySoloTests(MirrorMultiThreadTest) @@ -185,6 +187,17 @@ func VppConnectProxyGetTest(s *VppProxySuite) { s.CurlDownloadResourceViaTunnel(targetUri, proxyUri) } +func VppConnectProxyConnectionFailedMTTest(s *VppProxySuite) { + var proxyPort uint16 = 8080 + s.SetupNginxServer() + configureVppProxy(s, "http", proxyPort) + + targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.ServerPort()+1) + proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), proxyPort) + _, log := s.CurlRequestViaTunnel(targetUri, proxyUri) + s.AssertContains(log, "HTTP/1.1 502 Bad Gateway") +} + func VppConnectProxyPutTest(s *VppProxySuite) { var proxyPort uint16 = 8080 s.SetupNginxServer() @@ -373,3 +386,246 @@ func VppProxyUdpMigrationMTTest(s *VppUdpProxySuite) { s.Log(s.Containers.VppProxy.VppInstance.Vppctl("show session verbose 2")) } + +func VppConnectUdpProxyTest(s *VppUdpProxySuite) { + remoteServerConn := s.StartEchoServer() + defer remoteServerConn.Close() + + vppProxy := s.Containers.VppProxy.VppInstance + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + s.Log(vppProxy.Vppctl(cmd)) + + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.ProxyPort(), s.ServerAddr(), s.ServerPort()) + c := s.NewConnectUdpClient(s.MaxTimeout, true) + err := c.Dial(proxyAddress, targetUri) + s.AssertNil(err, fmt.Sprint(err)) + defer c.Close() + + data := []byte("hello") + + err = c.WriteDgramCapsule(data) + s.AssertNil(err, fmt.Sprint(err)) + payload, err := c.ReadDgramCapsule() + s.AssertNil(err, fmt.Sprint(err)) + s.AssertEqual(data, payload) +} + +func VppConnectUdpInvalidCapsuleTest(s *VppUdpProxySuite) { + remoteServerConn := s.StartEchoServer() + defer remoteServerConn.Close() + + vppProxy := s.Containers.VppProxy.VppInstance + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + s.Log(vppProxy.Vppctl(cmd)) + + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.ProxyPort(), s.ServerAddr(), s.ServerPort()) + c := s.NewConnectUdpClient(s.MaxTimeout, true) + err := c.Dial(proxyAddress, targetUri) + s.AssertNil(err, fmt.Sprint(err)) + defer c.Close() + + // Capsule length is set to 494878333 which exceed maximum allowed UDP payload length 65527 and connection must be aborted + capsule := []byte{ + 0x00, // type + 0x9D, 0x7F, 0x3E, 0x7D, // length + 0x00, // context ID + 0x4B, 0x6E, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x6F, 0x66, 0x20, 0x4E, 0x69, // some extra junk + } + n, err := c.Conn.Write(capsule) + s.AssertNil(err, fmt.Sprint(err)) + s.AssertEqual(n, len(capsule)) + b := make([]byte, 1) + _, err = c.Conn.Read(b) + s.AssertMatchError(err, io.EOF, "connection not closed by proxy") +} + +func VppConnectUdpUnknownCapsuleTest(s *VppUdpProxySuite) { + remoteServerConn := s.StartEchoServer() + defer remoteServerConn.Close() + + vppProxy := s.Containers.VppProxy.VppInstance + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + s.Log(vppProxy.Vppctl(cmd)) + + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.ProxyPort(), s.ServerAddr(), s.ServerPort()) + c := s.NewConnectUdpClient(s.MaxTimeout, true) + err := c.Dial(proxyAddress, targetUri) + s.AssertNil(err, fmt.Sprint(err)) + defer c.Close() + + // Send capsule with unknown type 0x40 which is outside range for standards (0x00 - 0x3f) + // Endpoint that receives capsule with unknown type must silently drop that capsule and skip over to parse the next capsule + err = c.WriteCapsule(0x4040, []byte("None shall pass")) + s.AssertNil(err, fmt.Sprint(err)) + + // Send valid capsule to verify that previous was dropped + data := []byte("hello") + err = c.WriteDgramCapsule(data) + s.AssertNil(err, fmt.Sprint(err)) + payload, err := c.ReadDgramCapsule() + s.AssertNil(err, fmt.Sprint(err)) + s.AssertEqual(data, payload) +} + +func VppConnectUdpClientCloseTest(s *VppUdpProxySuite) { + remoteServerConn := s.StartEchoServer() + defer remoteServerConn.Close() + + vppProxy := s.Containers.VppProxy.VppInstance + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + s.Log(vppProxy.Vppctl(cmd)) + + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.ProxyPort(), s.ServerAddr(), s.ServerPort()) + c := s.NewConnectUdpClient(s.MaxTimeout, true) + err := c.Dial(proxyAddress, targetUri) + s.AssertNil(err, fmt.Sprint(err)) + + err = c.Close() + s.AssertNil(err, fmt.Sprint(err)) + proxyClientConn := fmt.Sprintf("[T] %s:%d->%s", s.VppProxyAddr(), s.ProxyPort(), s.ClientAddr()) + proxyTargetConn := fmt.Sprintf("[U] %s:", s.Interfaces.Server.Peer.Ip4AddressString()) + for nTries := 0; nTries < 10; nTries++ { + o := vppProxy.Vppctl("show session verbose 2") + if !strings.Contains(o, proxyClientConn) { + break + } + time.Sleep(1 * time.Second) + } + sessions := vppProxy.Vppctl("show session verbose 2") + s.Log(sessions) + s.AssertNotContains(sessions, proxyClientConn, "client-proxy session not closed") + s.AssertNotContains(sessions, proxyTargetConn, "proxy-server session not closed") +} + +func vppConnectUdpStressLoad(s *VppUdpProxySuite) { + var ( + connectError, timeout, readError, writeError, invalidData, total atomic.Uint32 + wg sync.WaitGroup + ) + + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) + targetUri := fmt.Sprintf("http://%s/.well-known/masque/udp/%s/%d/", proxyAddress, s.ServerAddr(), s.ServerPort()) + + // warm-up + warmUp := s.NewConnectUdpClient(s.MaxTimeout, false) + err := warmUp.Dial(proxyAddress, targetUri) + s.AssertNil(err, fmt.Sprint(err)) + defer warmUp.Close() + data := []byte("hello") + err = warmUp.WriteDgramCapsule(data) + s.AssertNil(err, fmt.Sprint(err)) + payload, err := warmUp.ReadDgramCapsule() + s.AssertNil(err, fmt.Sprint(err)) + s.AssertEqual(data, payload) + warmUp.Close() + + stop := make(chan struct{}) + + s.Log("Running 30s test @ " + targetUri) + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + var tot, timed, re, we uint32 + defer wg.Done() + defer func() { + total.Add(tot) + timeout.Add(timed) + readError.Add(re) + writeError.Add(we) + }() + restart: + c := s.NewConnectUdpClient(s.MaxTimeout, false) + e := c.Dial(proxyAddress, targetUri) + if e != nil { + connectError.Add(1) + return + } + defer c.Close() + + req := make([]byte, 64) + rand.Read(req) + + for { + select { + default: + tot += 1 + e = c.WriteDgramCapsule(req) + if e != nil { + if errors.Is(e, os.ErrDeadlineExceeded) { + timed += 1 + } else { + we += 1 + } + continue + } + resp, e := c.ReadDgramCapsule() + if e != nil { + if errors.Is(e, os.ErrDeadlineExceeded) { + timed += 1 + } else if errors.Is(e, err.(*CapsuleParseError)) { + invalidData.Add(1) + } else { + re += 1 + } + c.Close() + goto restart + } + if bytes.Compare(req, resp) != 0 { + invalidData.Add(1) + c.Close() + goto restart + } + case <-stop: + return + } + } + }() + } + for i := 0; i < 30; i++ { + GinkgoWriter.Print(".") + time.Sleep(time.Second) + } + GinkgoWriter.Print("\n") + close(stop) // tell clients to stop + wg.Wait() // wait until clients finish + successRatio := (float64(total.Load()-(timeout.Load()+readError.Load()+writeError.Load()+invalidData.Load())) / float64(total.Load())) * 100.0 + summary := fmt.Sprintf("1000 connections %d requests in 30s", total.Load()) + report := fmt.Sprintf("Requests/sec: %d\n", total.Load()/30) + report += fmt.Sprintf("Errors: timeout %d, read %d, write %d, invalid data received %d, connection %d\n", timeout.Load(), readError.Load(), writeError.Load(), invalidData.Load(), connectError.Load()) + report += fmt.Sprintf("Successes ratio: %.2f%%\n", successRatio) + AddReportEntry(summary, report) + s.AssertGreaterThan(successRatio, 90.0) +} + +func VppConnectUdpStressTest(s *VppUdpProxySuite) { + remoteServerConn := s.StartEchoServer() + defer remoteServerConn.Close() + + vppProxy := s.Containers.VppProxy.VppInstance + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + s.Log(vppProxy.Vppctl(cmd)) + + // no goVPP less noise + vppProxy.Disconnect() + + vppConnectUdpStressLoad(s) +} + +func VppConnectUdpStressMTTest(s *VppUdpProxySuite) { + remoteServerConn := s.StartEchoServer() + defer remoteServerConn.Close() + + vppProxy := s.Containers.VppProxy.VppInstance + vppProxy.Disconnect() + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + s.Log(vppProxy.Vppctl(cmd)) + + // no goVPP less noise + vppProxy.Disconnect() + + vppConnectUdpStressLoad(s) +} |