diff --git a/README.md b/README.md index 9096d67..8d44046 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,14 @@ and any ports using `https` will be using the Tailscale issued certificate. ## Configuration -In order to configure it, you will need to setup a configuration file such -as the following: +### Example + +In the following example, we will configure the `tailscale-proxy` to proxy +all traffic from a device named `bmc-example-server` to the IP address +`1.2.3.4` and will also proxy the ports `443` and `5900` using a Tailscale +issued certificate. + +The device will also be tagged with `bmc` and `example` tags. ```yaml endpoints: @@ -21,10 +27,33 @@ endpoints: type: tls - port: 5900 type: tls + - port: 623 + type: udp ``` -In the example above, a Tailscale device will be registered with the name -of `bmc-example-server` and all of it's traffic will be proxied to the IP -address `1.2.3.4`. The tags assigned to it within Tailscale will be both -`bmc` and `example`. In addition, the port `443` and `5900` will be proxied -using a Tailscale issued certificate (through LetsEncrypt). +### Options + +#### `endpoints` + +In this section, you can configure all of the endpoints that will be proxied +by the `tailscale-proxy`. + +##### `hostname` + +This is the hostname that will be used to register the device with Tailscale. + +##### `ip` + +This is the IP address that the device will be proxied to. + +##### `tags` + +This is a list of tags that will be assigned to the device within Tailscale. + +##### `listeners` + +This is a list of ports that will be proxied by the `tailscale-proxy`, with +the type being any of the following: + +- `tls`: This will proxy the port using a Tailscale issued certificate. +- `udp`: This will proxy UDP traffic to the target device. diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index c14806f..6046a04 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -81,7 +81,7 @@ func (ep *Endpoint) Start() { }) for _, ln := range ep.Listeners { - ln.Start(ep.IP, srv) + go ln.Start(ep.IP, srv) } } diff --git a/endpoint/listener.go b/endpoint/listener.go index e4e72d2..0ab5e05 100644 --- a/endpoint/listener.go +++ b/endpoint/listener.go @@ -3,15 +3,19 @@ package endpoint import ( "crypto/tls" "fmt" + "io" "log" + "net" "net/http" "net/http/httputil" "net/url" + "sync" "github.com/vexxhost/tailscale-proxy/internal/tsnet" + "tailscale.com/types/nettype" ) -func (l *Listener) Start(ip string, srv *tsnet.Server) { +func (l *Listener) startTLS(ip string, srv *tsnet.Server) { ln, err := srv.ListenTLS("tcp", fmt.Sprintf(":%d", l.Port)) if err != nil { log.Fatal(err) @@ -58,9 +62,88 @@ func (l *Listener) Start(ip string, srv *tsnet.Server) { http.Error(w, err.Error(), http.StatusBadGateway) } - go func() { - log.Fatal(http.Serve(ln, rp)) - }() + log.Fatal(http.Serve(ln, rp)) +} + +func (l *Listener) startUDP(ip string, srv *tsnet.Server) { + ln, err := srv.Listen("udp", fmt.Sprintf(":%d", l.Port)) + if err != nil { + log.Fatal(err) + } + + l.listener = ln + + for { + conn, err := l.listener.Accept() + if err != nil { + log.Print(err) + continue + } + + go func(c net.Conn) { + defer c.Close() + + packet := c.(nettype.ConnPacketConn) + buf := make([]byte, 1500) + + for { + n, _, err := packet.ReadFrom(buf) + if err != nil { + log.Print(err) + return + } + + if n == 0 { + continue + } + + remoteAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, l.Port)) + if err != nil { + log.Print(err) + return + } + + remoteConn, err := net.DialUDP("udp", nil, remoteAddr) + if err != nil { + log.Print(err) + return + } + defer remoteConn.Close() + + _, err = remoteConn.Write(buf[:n]) + if err != nil { + log.Print(err) + return + } + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + io.Copy(remoteConn, packet) + }() + + go func() { + defer wg.Done() + io.Copy(packet, remoteConn) + }() + + wg.Wait() + } + }(conn) + } +} + +func (l *Listener) Start(ip string, srv *tsnet.Server) { + switch l.Type { + case ListenerTypeUDP: + l.startUDP(ip, srv) + case ListenerTypeTLS: + l.startTLS(ip, srv) + default: + log.Fatalf("unsupported listener type %q", l.Type) + } } func (l *Listener) Close() { diff --git a/endpoint/types.go b/endpoint/types.go index 413b858..a5abbf1 100644 --- a/endpoint/types.go +++ b/endpoint/types.go @@ -13,6 +13,7 @@ type ListenerType string const ( ListenerTypeTLS ListenerType = "tls" + ListenerTypeUDP ListenerType = "udp" ) type Listener struct {