~kt/udprelay

ce19c157d817bfd42b1f26715d8967df96f61a87 — Katie Wolfe 1 year, 2 months ago f3fc153
Implement command protocol
5 files changed, 189 insertions(+), 36 deletions(-)

A asciiutils.go
M main.go
A relay.go
A udprelay.1.scd
M udprelay.5.scd
A asciiutils.go => asciiutils.go +36 -0
@@ 0,0 1,36 @@
package main

var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}

func TrimLeftSpace(buf []byte) []byte {
	for i := 0; len(buf) > 0; i++ {
		if asciiSpace[buf[0]] != 1 {
			break
		}
		buf = buf[1:]
	}
	return buf
}

func TrimRightSpace(buf []byte) []byte {
	for i := 0; len(buf) > 0; i++ {
		if asciiSpace[buf[len(buf)-1]] != 1 {
			break
		}
		buf = buf[:len(buf)-1]
	}
	return buf
}

func TrimSpace(buf []byte) []byte {
	return TrimLeftSpace(TrimRightSpace(buf))
}

func Split2Space(buf []byte) ([]byte, []byte) {
	for i := 0; i < len(buf); i++ {
		if asciiSpace[buf[i]] == 1 {
			return buf[:i], buf[i+1:]
		}
	}
	return buf, []byte{}
}

M main.go => main.go +8 -35
@@ 11,11 11,7 @@ import (
)

var flagTimeout = flag.String("timeout", "10m", "duration to keep connections alive after their last packet")

type Peer struct {
	Addr    *net.UDPAddr
	Timeout time.Time
}
var flagProtocol = flag.Bool("protocol", false, "enables the udprelay command protocol")

func main() {
	log.SetFlags(0)


@@ 41,14 37,18 @@ func main() {
		os.Exit(2)
	}

	peers := make(map[string]*Peer, 0)

	conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: listenPort})
	if err != nil {
		panic(err)
	}
	log.Printf("listen: %d\n", listenPort)

	relay := &Relay{
		Log:             log.New(os.Stderr, "", 0),
		Timeout:         timeoutDuration,
		CommandProtocol: *flagProtocol,
	}

	buf := make([]byte, 65536)
	for {
		n, addr, err := conn.ReadFromUDP(buf)


@@ 58,33 58,6 @@ func main() {
		}
		packet := buf[:n]

		peer, exists := peers[addr.String()]
		if !exists {
			log.Printf("connect: %s\n", addr.String())
			peer = &Peer{
				Addr: addr,
			}
			peers[addr.String()] = peer
		}
		peer.Timeout = time.Now().Add(timeoutDuration)

		sender := peer
		for addr, peer := range peers {
			if peer.Addr == sender.Addr {
				continue
			}

			if time.Now().After(peer.Timeout) {
				log.Printf("timeout: %s\n", addr)
				delete(peers, addr)
				continue
			}

			_, err := conn.WriteToUDP(packet, peer.Addr)
			if err != nil {
				log.Printf("error: writing to %s: %s\n", peer.Addr.String(), err)
				continue
			}
		}
		relay.HandlePacket(conn, addr, packet)
	}
}

A relay.go => relay.go +117 -0
@@ 0,0 1,117 @@
package main

import (
	"bytes"
	"log"
	"net"
	"time"
)

var cmdPrefix = []byte("udprelay!")

type Relay struct {
	Log             *log.Logger
	CommandProtocol bool

	Timeout time.Duration

	peers    map[string]*Peer
	channels map[string]*Channel
}

type Channel struct {
	Peers map[string]*Peer
}

type Peer struct {
	Addr    *net.UDPAddr
	Timeout time.Time
	Channel string
}

func (relay *Relay) HandlePacket(conn *net.UDPConn, addr *net.UDPAddr, packet []byte) {
	peer := relay.peers[addr.String()]
	if peer == nil {
		peer = &Peer{
			Addr:    addr,
			Channel: "",
		}
		if relay.peers == nil {
			relay.peers = make(map[string]*Peer)
			relay.channels = make(map[string]*Channel)
			relay.channels[""] = &Channel{
				Peers: make(map[string]*Peer),
			}
		}
		channel := relay.channels[""]
		channel.Peers[peer.Addr.String()] = peer
		relay.peers[addr.String()] = peer
	}
	peer.Timeout = time.Now().Add(relay.Timeout)

	if relay.CommandProtocol {
		if bytes.HasPrefix(packet, cmdPrefix) {
			cmd := packet[len(cmdPrefix):]
			cmd, args := Split2Space(cmd)
			args = TrimSpace(args)
			relay.handleCommand(conn, packet, peer, string(cmd), args)
			return
		}
	}

	sender := peer
	for _, peer := range relay.channels[peer.Channel].Peers {
		if peer.Addr == sender.Addr {
			continue
		}

		if time.Now().After(peer.Timeout) {
			relay.dropPeer(peer)
			continue
		}

		_, err := conn.WriteToUDP(packet, peer.Addr)
		if err != nil {
			relay.Log.Printf("error: writing to %s: %s\n", peer.Addr.String(), err)
			continue
		}
	}
}

func (relay *Relay) dropPeer(peer *Peer) {
	relay.switchChannel(peer, "")
	delete(relay.peers, peer.Addr.String())
}

func (relay *Relay) switchChannel(peer *Peer, channelName string) {
	delete(relay.channels[peer.Channel].Peers, peer.Addr.String())
	if !(peer.Channel == "") && len(relay.channels[peer.Channel].Peers) == 0 {
		delete(relay.channels, peer.Channel)
	}
	peer.Channel = channelName
	channel := relay.channels[channelName]
	if channel == nil {
		channel = &Channel{
			Peers: make(map[string]*Peer),
		}
		relay.channels[channelName] = channel
	}
	channel.Peers[peer.Addr.String()] = peer
}

func (relay *Relay) handleCommand(conn *net.UDPConn, packet []byte, peer *Peer, cmd string, args []byte) {
	switch cmd {
	case "echo":
		_, err := conn.WriteToUDP(packet, peer.Addr)
		if err != nil {
			relay.Log.Printf("error: replying to ping: %s\n", err)
		}
	case "channel":
		relay.switchChannel(peer, string(args))

		_, err := conn.WriteToUDP(packet, peer.Addr)
		if err != nil {
			relay.Log.Printf("error: replying to channel switch message: %s\n", err)
		}
	}
}

A udprelay.1.scd => udprelay.1.scd +27 -0
@@ 0,0 1,27 @@
udprelay(1)

# NAME

udprelay \- server to relay UDP connections to connected peers.

# SYNOPSIS

*udprelay* [_option_...] _port_

# DESCRIPTION

udprelay accepts UDP connections on _port_ and relays all incoming data to every peer which it has received data from within a certain timeframe defined by the *-timeout* option. When it hasn't received a packet from a peer in that time, it quietly drops all internal state related to that peer and stops relaying packets to it until it receives a packet from it again.

udprelay also features an optional command protocol that allows for more advanced functionality such as channels. This protocol is documented in *udprelay*(5) and may be enabled by passing the *-protocol* flag.

# OPTIONS

*-protocol*
	Enables the udprelay command protocol. When enabled, packets beginning with the string *udprelay!* will not be relayed and will instead be handled by udprelay in accordance with the protocol defined in *udprelay*(5). When unset, all packets will always be relayed.

*-timeout* _duration_
	Set the amount of time to leave connections open without receiving any packets from a peer. _duration_ is a sequence of decimal numbers with unit suffixes, such as *10m*, *120s*, and *5m48s*.

# SEE ALSO

*udprelay*(5)

M udprelay.5.scd => udprelay.5.scd +1 -1
@@ 18,7 18,7 @@ A packet starting with the string _udprelay!_ will never be relayed to other pee

## channel

The *channel* command switches the peer to a different channel identified by the first string of non-spacing-characters in the command's _body_. If the _body_ is empty, the peer is switched to the default unnamed channel. Peers will only receive and send messages sent by and to peers who have connected to the same channel.
The *channel* command switches the peer to a different channel identified by the first string of non-spacing-characters in the command's _body_. If the _body_ is empty, the peer is switched to the default unnamed channel. Peers will only receive and send messages sent by and to peers who have connected to the same channel. After switching the peer's channel, the server will send the command's packet back to its originator unchanged.

## echo