// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>// SPDX-License-Identifier: MITpackage vnetimport ()// 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 |// : | | : +---------+ +---------+ +--------+// : | | ............................:// : +--------+ :// ...............................................typeUDPProxystruct {// 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 ( *Router) (*UDPProxy, error) { := &UDPProxy{router: , timeout: 2 * time.Minute}return , nil}// Close the proxy, stop all workers.func ( *UDPProxy) () error { .workers.Range(func(, interface{}) bool { _ = .(*aUDPProxyWorker).Close() //nolint:forcetypeassertreturntrue })returnnil}// Proxy starts a worker for server, ignore if already started.func ( *UDPProxy) ( *Net, *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 , := .workers.Load(.String()); {// nolint:godox // TODO: Need to restart the stopped worker?returnnil }// Not exists, create a new one. := &aUDPProxyWorker{router: .router, mockRealServerAddr: .mockRealServerAddr, }// Create context for cleanup.varcontext.Context , .ctxDisposeCancel = context.WithCancel(context.Background()) .workers.Store(.String(), )return .Proxy(, , )}// 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// For cleanup. ctxDisposeCancel context.CancelFunc wg sync.WaitGroup}func ( *aUDPProxyWorker) () error {// Notify all goroutines to dispose. .ctxDisposeCancel()// Wait for all goroutines quit. .wg.Wait()returnnil}func ( *aUDPProxyWorker) ( context.Context, *Net, *net.UDPAddr) error { // nolint:gocognit// Create vnet for real server by serverAddr. , := NewNet(&NetConfig{StaticIP: .IP.String(), })if != nil {return }if = .router.AddNet(); != nil {return }// We must create a "same" vnet.UDPConn as the net.UDPConn, // which has the same ip:port, to copy packets between them. , := .ListenUDP("udp4", )if != nil {return }// User stop proxy, we should close the socket.gofunc() { <-.Done() _ = .Close() }()// Got new vnet client, start a new endpoint. := func( net.Addr) (*net.UDPConn, error) {// Exists binding.if , := .endpoints.Load(.String()); {// Exists endpoint, reuse it.return .(*net.UDPConn), nil//nolint:forcetypeassert }// The real server we proxy to, for utest to mock it. := if .mockRealServerAddr != nil { = .mockRealServerAddr }// Got new vnet client, create new endpoint. , := net.DialUDP("udp4", nil, )if != nil {returnnil, }// User stop proxy, we should close the socket.gofunc() { <-.Done() _ = .Close() }()// Bind address. .endpoints.Store(.String(), )// Got packet from real serverAddr, we should proxy it to vnet. .wg.Add(1)gofunc( net.Addr) {defer .wg.Done() := make([]byte, 1500)for { , , := .ReadFrom()if != nil {return }if <= 0 {continue// Drop packet }if , := .WriteTo([:], ); != nil {return } } }()return , nil }// Start a proxy goroutine. .wg.Add(1)gofunc() {defer .wg.Done() := make([]byte, 1500)for { , , := .ReadFrom()if != nil {return }if <= 0 || == nil {continue// Drop packet } , := ()if != nil {continue// Drop packet. }if , := .Write([:]); != nil {return } } }()returnnil}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.