-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from guillaumerose/udp
Add UDP support in the forwarder
- Loading branch information
Showing
5 changed files
with
301 additions
and
33 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,161 @@ | ||
package forwarder | ||
|
||
// Modified version of https://github.com/moby/moby/blob/master/cmd/docker-proxy/udp_proxy.go and | ||
// https://github.com/moby/vpnkit/blob/master/go/pkg/libproxy/udp_proxy.go | ||
|
||
import ( | ||
"encoding/binary" | ||
"net" | ||
"strings" | ||
"sync" | ||
"syscall" | ||
"time" | ||
|
||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
const ( | ||
// UDPConnTrackTimeout is the timeout used for UDP connection tracking | ||
UDPConnTrackTimeout = 90 * time.Second | ||
// UDPBufSize is the buffer size for the UDP proxy | ||
UDPBufSize = 65507 | ||
) | ||
|
||
// A net.Addr where the IP is split into two fields so you can use it as a key | ||
// in a map: | ||
type connTrackKey struct { | ||
IPHigh uint64 | ||
IPLow uint64 | ||
Port int | ||
} | ||
|
||
func newConnTrackKey(addr *net.UDPAddr) *connTrackKey { | ||
if len(addr.IP) == net.IPv4len { | ||
return &connTrackKey{ | ||
IPHigh: 0, | ||
IPLow: uint64(binary.BigEndian.Uint32(addr.IP)), | ||
Port: addr.Port, | ||
} | ||
} | ||
return &connTrackKey{ | ||
IPHigh: binary.BigEndian.Uint64(addr.IP[:8]), | ||
IPLow: binary.BigEndian.Uint64(addr.IP[8:]), | ||
Port: addr.Port, | ||
} | ||
} | ||
|
||
type connTrackMap map[connTrackKey]net.Conn | ||
|
||
// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy | ||
// interface to handle UDP traffic forwarding between the frontend and backend | ||
// addresses. | ||
type UDPProxy struct { | ||
listener *net.UDPConn | ||
dialer func() (net.Conn, error) | ||
connTrackTable connTrackMap | ||
connTrackLock sync.Mutex | ||
} | ||
|
||
// NewUDPProxy creates a new UDPProxy. | ||
func NewUDPProxy(listener *net.UDPConn, dialer func() (net.Conn, error)) (*UDPProxy, error) { | ||
return &UDPProxy{ | ||
listener: listener, | ||
connTrackTable: make(connTrackMap), | ||
dialer: dialer, | ||
}, nil | ||
} | ||
|
||
func (proxy *UDPProxy) replyLoop(proxyConn net.Conn, clientAddr *net.UDPAddr, clientKey *connTrackKey) { | ||
defer func() { | ||
proxy.connTrackLock.Lock() | ||
delete(proxy.connTrackTable, *clientKey) | ||
proxy.connTrackLock.Unlock() | ||
proxyConn.Close() | ||
}() | ||
|
||
readBuf := make([]byte, UDPBufSize) | ||
for { | ||
_ = proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout)) | ||
again: | ||
read, err := proxyConn.Read(readBuf) | ||
if err != nil { | ||
if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED { | ||
// This will happen if the last write failed | ||
// (e.g: nothing is actually listening on the | ||
// proxied port on the container), ignore it | ||
// and continue until UDPConnTrackTimeout | ||
// expires: | ||
goto again | ||
} | ||
return | ||
} | ||
for i := 0; i != read; { | ||
written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr) | ||
if err != nil { | ||
return | ||
} | ||
i += written | ||
} | ||
} | ||
} | ||
|
||
// Run starts forwarding the traffic using UDP. | ||
func (proxy *UDPProxy) Run() { | ||
readBuf := make([]byte, UDPBufSize) | ||
for { | ||
read, from, err := proxy.listener.ReadFromUDP(readBuf) | ||
if err != nil { | ||
// NOTE: Apparently ReadFrom doesn't return | ||
// ECONNREFUSED like Read do (see comment in | ||
// UDPProxy.replyLoop) | ||
if !isClosedError(err) { | ||
log.Printf("Stopping udp proxy (%s)", err) | ||
} | ||
break | ||
} | ||
|
||
fromKey := newConnTrackKey(from) | ||
proxy.connTrackLock.Lock() | ||
proxyConn, hit := proxy.connTrackTable[*fromKey] | ||
if !hit { | ||
proxyConn, err = proxy.dialer() | ||
if err != nil { | ||
log.Printf("Can't proxy a datagram to udp: %s\n", err) | ||
proxy.connTrackLock.Unlock() | ||
continue | ||
} | ||
proxy.connTrackTable[*fromKey] = proxyConn | ||
go proxy.replyLoop(proxyConn, from, fromKey) | ||
} | ||
proxy.connTrackLock.Unlock() | ||
for i := 0; i != read; { | ||
written, err := proxyConn.Write(readBuf[i:read]) | ||
if err != nil { | ||
log.Printf("Can't proxy a datagram to udp: %s\n", err) | ||
break | ||
} | ||
i += written | ||
} | ||
} | ||
} | ||
|
||
// Close stops forwarding the traffic. | ||
func (proxy *UDPProxy) Close() error { | ||
proxy.listener.Close() | ||
proxy.connTrackLock.Lock() | ||
defer proxy.connTrackLock.Unlock() | ||
for _, conn := range proxy.connTrackTable { | ||
conn.Close() | ||
} | ||
return nil | ||
} | ||
|
||
func isClosedError(err error) bool { | ||
/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing. | ||
* See: | ||
* http://golang.org/src/pkg/net/net.go | ||
* https://code.google.com/p/go/issues/detail?id=4337 | ||
* https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ | ||
*/ | ||
return strings.HasSuffix(err.Error(), "use of closed network connection") | ||
} |
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 |
---|---|---|
@@ -1,10 +1,19 @@ | ||
package types | ||
|
||
type TransportProtocol string | ||
|
||
const ( | ||
UDP TransportProtocol = "udp" | ||
TCP TransportProtocol = "tcp" | ||
) | ||
|
||
type ExposeRequest struct { | ||
Local string `json:"local"` | ||
Remote string `json:"remote"` | ||
Local string `json:"local"` | ||
Remote string `json:"remote"` | ||
Protocol TransportProtocol `json:"protocol"` | ||
} | ||
|
||
type UnexposeRequest struct { | ||
Local string `json:"local"` | ||
Local string `json:"local"` | ||
Protocol TransportProtocol `json:"protocol"` | ||
} |
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
Oops, something went wrong.