Skip to content

Commit

Permalink
Add support for udp listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
mnaser committed Jan 9, 2025
1 parent 4366540 commit 4062dc4
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 12 deletions.
43 changes: 36 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
2 changes: 1 addition & 1 deletion endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (ep *Endpoint) Start() {
})

for _, ln := range ep.Listeners {
ln.Start(ep.IP, srv)
go ln.Start(ep.IP, srv)
}
}

Expand Down
91 changes: 87 additions & 4 deletions endpoint/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions endpoint/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type ListenerType string

const (
ListenerTypeTLS ListenerType = "tls"
ListenerTypeUDP ListenerType = "udp"
)

type Listener struct {
Expand Down

0 comments on commit 4062dc4

Please sign in to comment.