Skip to content

Commit

Permalink
Support UDPProxy to communicate with real servers
Browse files Browse the repository at this point in the history
It make vnet be able to talk with any server,
which is written by other languages, might
not Go.
  • Loading branch information
winlinvip authored and Sean-Der committed Mar 12, 2021
1 parent 8970439 commit c0ffc29
Show file tree
Hide file tree
Showing 5 changed files with 884 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contribu
* [ZHENK](https://github.com/scorpionknifes)
* [OrlandoCo](https://github.com/OrlandoCo)
* [Tarrence van As](https://github.com/tarrencev)
* [Winlin](https://github.com/ossrs/srs) - *UDP proxy to communicate with real servers*

### License
MIT License - see [LICENSE](LICENSE) for full text
30 changes: 30 additions & 0 deletions examples/vnet-udpproxy/README
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
```
81 changes: 81 additions & 0 deletions examples/vnet-udpproxy/main.go
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)
}
176 changes: 176 additions & 0 deletions vnet/udpproxy.go
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
}
Loading

0 comments on commit c0ffc29

Please sign in to comment.