udphole

Basic UDP wormhole proxy
git clone git://git.finwo.net/app/udphole
Log | Files | Refs | README | LICENSE

README.md (7653B)


      1 # UDPHOLE
      2 
      3 A generic session-based UDP wormhole proxy: forward UDP packets between sockets using a simple RESP2 API
      4 
      5 ---
      6 
      7 ## Building
      8 
      9 Requirements: [dep](https://github.com/finwo/dep), C compiler.
     10 
     11 ```bash
     12 make
     13 ```
     14 
     15 The binary is `udphole`.
     16 
     17 ---
     18 
     19 ## Global options
     20 
     21 These options apply to all commands and must appear **before** the command name.
     22 
     23 | Option | Short | Description |
     24 |--------|-------|-------------|
     25 | `--config <path>` | `-f` | Config file path. If omitted, the following are tried in order: `$HOME/.config/udphole.conf`, `$HOME/.udphole.conf`, `/etc/udphole/udphole.conf`, `/etc/udphole.conf`. |
     26 | `--verbosity <level>` | `-v` | Log verbosity: fatal, error, warn, info, debug, trace (default: info). |
     27 | `--log <path>` | | Also write log to file (SIGHUP reopens for logrotate). |
     28 
     29 ---
     30 
     31 ## Running the daemon
     32 
     33 The main entry point is the **daemon** command.
     34 
     35 ```bash
     36 # Foreground (default, config auto-detected)
     37 ./udphole daemon
     38 
     39 # Explicit config file
     40 ./udphole -f /etc/udphole.conf daemon
     41 
     42 # Background (daemonize)
     43 ./udphole -f /etc/udphole.conf daemon -d
     44 
     45 # Force foreground even if config has daemonize=1
     46 ./udphole -f /etc/udphole.conf daemon -D
     47 ```
     48 
     49 | Option | Short | Description |
     50 |--------|--------|--------------|
     51 | `--daemonize` | `-d` | Run in background (double fork, detach from terminal). |
     52 | `--no-daemonize` | `-D` | Force foreground; overrides `daemonize=1` in config. |
     53 
     54 Daemonize behaviour:
     55 
     56 - By default the daemon runs in the **foreground**.
     57 - It goes to the **background** only if `daemonize=1` is set in `[udphole]` **or** you pass `-d`/`--daemonize`.
     58 - `-D`/`--no-daemonize` always forces foreground.
     59 
     60 After starting, the daemon loads config, starts the UDP proxy manager, binds the API socket, and handles session/socket/forward management via RESP2 protocol. Logging goes to stderr (and optionally to a file if you use global `--log`).
     61 
     62 ---
     63 
     64 ## How it works
     65 
     66 UDP hole is a simple UDP packet forwarder. It creates "sessions" that contain "sockets" and "forwards":
     67 
     68 1. **Session**: A container for sockets and forwards. Has an idle timeout.
     69 2. **Socket**: A UDP socket that can either:
     70    - **Listen**: Binds to a port and learns the remote address from the first packet received (NAT traversal)
     71    - **Connect**: Binds to a port and connects to a fixed remote address
     72 3. **Forward**: Routes packets from a source socket to a destination socket
     73 
     74 ### Use cases
     75 
     76 - **NAT traversal**: A socket in listen mode learns the remote address from the first incoming packet, enabling symmetric NAT traversal
     77 - **Fixed forwarding**: Connect sockets to fixed IP:port for simple relay
     78 - **Session management**: Idle sessions expire automatically, useful for temporary forwards
     79 
     80 ---
     81 
     82 ## Example: simple UDP echo
     83 
     84 ```bash
     85 # Create a session with 60 second idle timeout
     86 session.create mysession 60
     87 
     88 # Create a listen socket (will learn remote from first packet)
     89 session.socket.create.listen mysession socket1
     90 
     91 # Get the port assigned to socket1 (response: [port, advertise_addr])
     92 # The port will be in range 7000-7999
     93 
     94 # From a remote client, send UDP packet to that port
     95 # The socket now "knows" the remote address
     96 
     97 # Create another socket to forward to
     98 session.socket.create.connect mysession socket2 8.8.8.8 53
     99 
    100 # Create forward: packets from socket1 -> socket2
    101 session.forward.create mysession socket1 socket2
    102 
    103 # Now packets received on socket1 are forwarded to 8.8.8.8:53
    104 # And responses are sent back to the original sender
    105 ```
    106 
    107 ---
    108 
    109 ## Example: symmetric NAT traversal
    110 
    111 ```bash
    112 # Create session
    113 session.create nat-traversal 300
    114 
    115 # Client A: create listen socket, gets port 7012
    116 session.socket.create.listen nat-traversal client-a
    117 
    118 # Client B: connect to client A's port
    119 session.socket.create.connect nat-traversal client-b <client-a-ip> 7012
    120 
    121 # Client B now receives packets from client A
    122 session.forward.create nat-traversal client-a client-b
    123 
    124 # Bidirectional
    125 session.forward.create nat-traversal client-b client-a
    126 ```
    127 
    128 ---
    129 
    130 ## Running the cluster
    131 
    132 The cluster command acts as a proxy to multiple backing udphole daemon nodes.
    133 
    134 ```bash
    135 # Foreground
    136 ./udphole cluster
    137 
    138 # Background
    139 ./udphole -f /etc/udphole.conf cluster -d
    140 
    141 # Force foreground
    142 ./udphole -f /etc/udphole.conf cluster -D
    143 ```
    144 
    145 | Option | Short | Description |
    146 |--------|--------|--------------|
    147 | `--daemonize` | `-d` | Run in background (double fork, detach from terminal). |
    148 | `--no-daemonize` | `-D` | Force foreground; overrides `daemonize=1` in config. |
    149 
    150 The cluster provides the **exact same API** as the regular daemon but forwards commands to backing nodes:
    151 
    152 - `session.create` - Routes to the node with the lowest weight/sessioncount ratio
    153 - `session.list` / `session.count` - Aggregates results from all nodes
    154 - `session.info` / `session.destroy` / `socket.*` / `forward.*` - Routes to the node that has the session
    155 
    156 The cluster holds no local state - it queries backing nodes on demand and performs healthchecks (every 5 seconds) to track node availability.
    157 
    158 ---
    159 
    160 ## Configuration
    161 
    162 ```ini
    163 [udphole]
    164 ports = 7000-7999
    165 listen = :12345
    166 advertise = 203.0.113.1
    167 
    168 [user:admin]
    169 secret = adminpass
    170 permit = *
    171 
    172 [user:*]
    173 permit = ping
    174 ```
    175 
    176 ### `[udphole]`
    177 
    178 | Option | Description |
    179 |--------|-------------|
    180 | `ports` | Port range for UDP sockets, as `low-high` (e.g. `7000-7999`). Default 7000–7999. (daemon only) |
    181 | `listen` | API server listen address. Can be repeated for multiple addresses. If not set, API server is disabled. |
    182 | `advertise` | Optional. IP address to advertise in API responses instead of the port number. Useful when behind NAT. (daemon only) |
    183 | `cluster` | Repeat for each backing node. Format: `tcp://[user:pass@]host:port` or `unix:///path/to/socket`. Enables cluster mode. |
    184 
    185 ### `[user:<name>]`
    186 
    187 | Option | Description |
    188 |--------|-------------|
    189 | `secret` | Password for authentication. |
    190 | `permit` | Space-separated list of command patterns the user can execute. Use `*` for all commands, `session.*` for all session commands, etc. |
    191 
    192 ---
    193 
    194 ## API commands
    195 
    196 The API uses the RESP2 (Redis) protocol. Connect with `redis-cli` or any Redis client library.
    197 
    198 | Command | Response |
    199 |---------|----------|
    200 | `auth username password` | `+OK` or `-ERR invalid credentials` |
    201 | `ping` | `+PONG` |
    202 | `quit` | `+OK`, closes connection |
    203 | `command` | List of commands accessible to the current user |
    204 
    205 ### Session commands
    206 
    207 | Command | Response |
    208 |---------|----------|
    209 | `session.create <id> [idle_expiry]` | `+OK` - creates session, optional idle expiry in seconds (default 60) |
    210 | `session.list` | Array of session IDs |
    211 | `session.info <id>` | Map with: session_id, created, last_activity, idle_expiry, sockets, forwards, fd_count, marked_for_deletion |
    212 | `session.destroy <id>` | `+OK` - destroys session and all its sockets/forwards |
    213 
    214 ### Socket commands
    215 
    216 | Command | Response |
    217 |---------|----------|
    218 | `session.socket.create.listen <session_id> <socket_id>` | Array: `[port, advertise_addr]` |
    219 | `session.socket.create.connect <session_id> <socket_id> <ip> <port>` | Array: `[port, advertise_addr]` |
    220 | `session.socket.destroy <session_id> <socket_id>` | `+OK` |
    221 
    222 ### Forward commands
    223 
    224 | Command | Response |
    225 |---------|----------|
    226 | `session.forward.list <session_id>` | Array of `[src_socket_id, dst_socket_id]` pairs |
    227 | `session.forward.create <session_id> <src_socket_id> <dst_socket_id>` | `+OK` |
    228 | `session.forward.destroy <session_id> <src_socket_id> <dst_socket_id>` | `+OK` |
    229 
    230 ### System commands
    231 
    232 | Command | Response |
    233 |---------|----------|
    234 | `system.load` | Array: `[1min, 5min, 15min]` load averages |
    235 | `session.count` | Integer: number of active sessions |
    236 
    237 ---
    238 
    239 A generic session-based UDP wormhole proxy.