summaryrefslogtreecommitdiff
path: root/vendor/github.com/pion/transport/vnet/udpproxy.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/pion/transport/vnet/udpproxy.go')
-rw-r--r--vendor/github.com/pion/transport/vnet/udpproxy.go176
1 files changed, 176 insertions, 0 deletions
diff --git a/vendor/github.com/pion/transport/vnet/udpproxy.go b/vendor/github.com/pion/transport/vnet/udpproxy.go
new file mode 100644
index 0000000..c774955
--- /dev/null
+++ b/vendor/github.com/pion/transport/vnet/udpproxy.go
@@ -0,0 +1,176 @@
+package vnet
+
+import (
+ "net"
+ "sync"
+ "time"
+)
+
+// UDPProxy is a proxy between real server(net.UDPConn) and vnet.UDPConn.
+//
+// High level design:
+// ..............................................
+// : Virtual Network (vnet) :
+// : :
+// +-------+ * 1 +----+ +--------+ :
+// | :App |------------>|:Net|--o<-----|:Router | .............................
+// +-------+ +----+ | | : UDPProxy :
+// : | | +----+ +---------+ +---------+ +--------+
+// : | |--->o--|:Net|-->o-| vnet. |-->o-| net. |--->-| :Real |
+// : | | +----+ | UDPConn | | UDPConn | | Server |
+// : | | : +---------+ +---------+ +--------+
+// : | | ............................:
+// : +--------+ :
+// ...............................................
+type UDPProxy struct {
+ // The router bind to.
+ router *Router
+
+ // Each vnet source, bind to a real socket to server.
+ // key is real server addr, which is net.Addr
+ // value is *aUDPProxyWorker
+ workers sync.Map
+
+ // For each endpoint, we never know when to start and stop proxy,
+ // so we stop the endpoint when timeout.
+ timeout time.Duration
+
+ // For utest, to mock the target real server.
+ // Optional, use the address of received client packet.
+ mockRealServerAddr *net.UDPAddr
+}
+
+// NewProxy create a proxy, the router for this proxy belongs/bind to. If need to proxy for
+// please create a new proxy for each router. For all addresses we proxy, we will create a
+// vnet.Net in this router and proxy all packets.
+func NewProxy(router *Router) (*UDPProxy, error) {
+ v := &UDPProxy{router: router, timeout: 2 * time.Minute}
+ return v, nil
+}
+
+// Close the proxy, stop all workers.
+func (v *UDPProxy) Close() error {
+ // nolint:godox // TODO: FIXME: Do cleanup.
+ return nil
+}
+
+// Proxy starts a worker for server, ignore if already started.
+func (v *UDPProxy) Proxy(client *Net, server *net.UDPAddr) error {
+ // Note that even if the worker exists, it's also ok to create a same worker,
+ // because the router will use the last one, and the real server will see a address
+ // change event after we switch to the next worker.
+ if _, ok := v.workers.Load(server.String()); ok {
+ // nolint:godox // TODO: Need to restart the stopped worker?
+ return nil
+ }
+
+ // Not exists, create a new one.
+ worker := &aUDPProxyWorker{
+ router: v.router, mockRealServerAddr: v.mockRealServerAddr,
+ }
+ v.workers.Store(server.String(), worker)
+
+ return worker.Proxy(client, server)
+}
+
+// A proxy worker for a specified proxy server.
+type aUDPProxyWorker struct {
+ router *Router
+ mockRealServerAddr *net.UDPAddr
+
+ // Each vnet source, bind to a real socket to server.
+ // key is vnet client addr, which is net.Addr
+ // value is *net.UDPConn
+ endpoints sync.Map
+}
+
+func (v *aUDPProxyWorker) Proxy(client *Net, serverAddr *net.UDPAddr) error { // nolint:gocognit
+ // Create vnet for real server by serverAddr.
+ nw := NewNet(&NetConfig{
+ StaticIP: serverAddr.IP.String(),
+ })
+ if err := v.router.AddNet(nw); err != nil {
+ return err
+ }
+
+ // We must create a "same" vnet.UDPConn as the net.UDPConn,
+ // which has the same ip:port, to copy packets between them.
+ vnetSocket, err := nw.ListenUDP("udp4", serverAddr)
+ if err != nil {
+ return err
+ }
+
+ // Got new vnet client, start a new endpoint.
+ findEndpointBy := func(addr net.Addr) (*net.UDPConn, error) {
+ // Exists binding.
+ if value, ok := v.endpoints.Load(addr.String()); ok {
+ // Exists endpoint, reuse it.
+ return value.(*net.UDPConn), nil
+ }
+
+ // The real server we proxy to, for utest to mock it.
+ realAddr := serverAddr
+ if v.mockRealServerAddr != nil {
+ realAddr = v.mockRealServerAddr
+ }
+
+ // Got new vnet client, create new endpoint.
+ realSocket, err := net.DialUDP("udp4", nil, realAddr)
+ if err != nil {
+ return nil, err
+ }
+
+ // Bind address.
+ v.endpoints.Store(addr.String(), realSocket)
+
+ // Got packet from real serverAddr, we should proxy it to vnet.
+ // nolint:godox // TODO: FIXME: Do cleanup.
+ go func(vnetClientAddr net.Addr) {
+ buf := make([]byte, 1500)
+ for {
+ n, _, err := realSocket.ReadFrom(buf)
+ if err != nil {
+ return
+ }
+
+ if n <= 0 {
+ continue // Drop packet
+ }
+
+ if _, err := vnetSocket.WriteTo(buf[:n], vnetClientAddr); err != nil {
+ return
+ }
+ }
+ }(addr)
+
+ return realSocket, nil
+ }
+
+ // Start a proxy goroutine.
+ // nolint:godox // TODO: FIXME: Do cleanup.
+ go func() {
+ buf := make([]byte, 1500)
+
+ for {
+ n, addr, err := vnetSocket.ReadFrom(buf)
+ if err != nil {
+ return
+ }
+
+ if n <= 0 || addr == nil {
+ continue // Drop packet
+ }
+
+ realSocket, err := findEndpointBy(addr)
+ if err != nil {
+ continue // Drop packet.
+ }
+
+ if _, err := realSocket.Write(buf[:n]); err != nil {
+ return
+ }
+ }
+ }()
+
+ return nil
+}