-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support UDPProxy to communicate with real servers
It make vnet be able to talk with any server, which is written by other languages, might not Go.
- Loading branch information
Showing
5 changed files
with
884 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# vnet-udpproxy | ||
|
||
This example demonstrates how VNet can be used to communicate with non-VNet addresses using UDPProxy. | ||
|
||
In this example we listen map the VNet Address `10.0.0.11` to a real address of our choice. We then | ||
send to our real address from three different VNet Addresses. | ||
|
||
If you pass `-address 192.168.1.3:8000` the traffic will be the following | ||
|
||
``` | ||
vnet(10.0.0.11:5787) => proxy => 192.168.1.3:8000 | ||
vnet(10.0.0.11:5788) => proxy => 192.168.1.3:8000 | ||
vnet(10.0.0.11:5789) => proxy => 192.168.1.3:8000 | ||
``` | ||
|
||
## Running | ||
``` | ||
go run main.go -address 192.168.1.3:8000 | ||
``` | ||
|
||
You should see the following in tcpdump | ||
``` | ||
sean@SeanLaptop:~/go/src/github.com/pion/transport/examples$ sudo tcpdump -i any udp and port 8000 | ||
tcpdump: data link type LINUX_SLL2 | ||
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode | ||
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes | ||
13:21:18.239943 lo In IP 192.168.1.7.40574 > 192.168.1.7.8000: UDP, length 5 | ||
13:21:18.240105 lo In IP 192.168.1.7.40647 > 192.168.1.7.8000: UDP, length 5 | ||
13:21:18.240304 lo In IP 192.168.1.7.57744 > 192.168.1.7.8000: UDP, length 5 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"net" | ||
"time" | ||
|
||
"github.com/pion/logging" | ||
"github.com/pion/transport/vnet" | ||
) | ||
|
||
func main() { | ||
address := flag.String("address", "", "Destination address that three separate vnet clients will send too") | ||
flag.Parse() | ||
|
||
// Create vnet WAN with one endpoint | ||
// See the following docs for more information | ||
// https://github.com/pion/transport/tree/master/vnet#example-wan-with-one-endpoint-vnet | ||
router, err := vnet.NewRouter(&vnet.RouterConfig{ | ||
CIDR: "0.0.0.0/0", | ||
LoggerFactory: logging.NewDefaultLoggerFactory(), | ||
}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Create a network and add to router, for example, for client. | ||
clientNetwork := vnet.NewNet(&vnet.NetConfig{ | ||
StaticIP: "10.0.0.11", | ||
}) | ||
if err = router.AddNet(clientNetwork); err != nil { | ||
panic(err) | ||
} | ||
|
||
if err = router.Start(); err != nil { | ||
panic(err) | ||
} | ||
defer router.Stop() // nolint:errcheck | ||
|
||
// Create a proxy, bind to the router. | ||
proxy, err := vnet.NewProxy(router) | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer proxy.Close() // nolint:errcheck | ||
|
||
serverAddr, err := net.ResolveUDPAddr("udp4", *address) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Start to proxy some addresses, clientNetwork is a hit for proxy, | ||
// that the client in vnet is from this network. | ||
if err = proxy.Proxy(clientNetwork, serverAddr); err != nil { | ||
panic(err) | ||
} | ||
|
||
// Now, all packets from client, will be proxy to real server, vice versa. | ||
client0, err := clientNetwork.ListenPacket("udp4", "10.0.0.11:5787") | ||
if err != nil { | ||
panic(err) | ||
} | ||
_, _ = client0.WriteTo([]byte("Hello"), serverAddr) | ||
|
||
client1, err := clientNetwork.ListenPacket("udp4", "10.0.0.11:5788") | ||
if err != nil { | ||
panic(err) | ||
} | ||
_, _ = client1.WriteTo([]byte("Hello"), serverAddr) | ||
|
||
client2, err := clientNetwork.ListenPacket("udp4", "10.0.0.11:5789") | ||
if err != nil { | ||
panic(err) | ||
} | ||
_, _ = client2.WriteTo([]byte("Hello"), serverAddr) | ||
|
||
// Packets are delivered by a goroutine so WriteTo | ||
// return doesn't mean success. This may improve in | ||
// the future. | ||
time.Sleep(time.Second * 3) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Oops, something went wrong.