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.