UdpConnector
UdpConnector is an outbound UDP client adapter. It creates a local UDP socket,
chooses a destination address and port, and forwards datagrams between the
previous node and the selected remote UDP peer.
Typical placement:
UdpListener -> ... -> UdpConnector -> remote UDP service
This node is normally used as the end of a UDP stream-style chain.
What It Does
- Creates a UDP socket bound to an ephemeral local port.
- Chooses a destination address and port from static settings, weighted destinations, or the line routing context.
- Resolves domain names through an internal asynchronous
DomainResolver. - Sends upstream payloads from the previous node to the remote UDP peer.
- Sends received UDP datagrams back downstream to the previous node.
- Drops datagrams from unexpected peers in
"connection"balance mode. - Tracks UDP lines with idle timeouts.
- Applies optional socket options such as socket buffers,
SO_MARK, device binding, and source-IP binding when supported.
UdpConnector behaves like a chain end. It is started by upstream init; it
does not need a next node.
Configuration Example
{
"name": "udp-out",
"type": "UdpConnector",
"settings": {
"address": "example.com",
"port": "random(40000,40100)",
"large-send-buffer": true,
"large-recv-buffer": true,
"fwmark": 10,
"interface": "eth0",
"source-ip": "192.0.2.10",
"domain-strategy": "prefer-ipv4"
}
}
Weighted Multi-Destination Example
{
"name": "udp-out",
"type": "UdpConnector",
"settings": {
"balance-mode": "packet",
"addresses": [
{
"address": "1.1.1.1",
"port": 53,
"weight": 3
},
{
"address": "8.8.8.8",
"port": "random(40000,40100)",
"weight": 1
}
],
"large-send-buffer": true,
"large-recv-buffer": true,
"domain-strategy": "prefer-ipv4"
}
}
balance-mode belongs directly inside settings, not inside each object in
addresses.
Required Fields
Top-level fields:
| Field | Type | Description |
|---|---|---|
name | string | User-chosen node name. Must be unique inside the config file. |
type | string | Must be exactly "UdpConnector". |
settings | object | Destination and socket configuration. |
settings must use exactly one destination style:
| Style | Required fields | Description |
|---|---|---|
| Single destination | address and port | Every line uses the same destination rule. |
| Weighted destinations | addresses | Each line or packet chooses one weighted destination object, depending on balance-mode. |
Do not mix addresses with top-level address or port.
Destination Selection
Single Destination
Use address and port directly under settings.
{
"settings": {
"address": "example.com",
"port": 53
}
}
Supported address values:
| Value | Behavior |
|---|---|
| IPv4 string | Send to that IPv4 address. |
| IPv6 string | Send to that IPv6 address. |
| Domain string | Resolve the domain before socket setup. |
"src_context->address" | Copy the source address from the line routing context. |
"dest_context->address" | Copy the destination address from the line routing context. |
Supported port values:
| Value | Behavior |
|---|---|
| number | Use that constant port. Must be 1 through 65535. |
| numeric string | Use that constant port, for example "53". |
"src_context->port" | Copy the source port from the line routing context. |
"dest_context->port" | Copy the destination port from the line routing context. |
"random(x,y)" | Choose a random port in the inclusive range [x, y]. |
random(x,y) requires valid ports, and x must be lower than or equal to y.
Weighted Destinations
Use addresses for weighted destination selection.
{
"settings": {
"addresses": [
{
"address": "1.1.1.1",
"port": 53,
"weight": 3
},
{
"address": "8.8.8.8",
"port": 53,
"weight": 1
}
]
}
}
Each object must contain:
| Field | Type | Description |
|---|---|---|
address | string | Same supported forms as single-destination address. |
port | number or string | Same supported forms as single-destination port, including random(x,y). |
weight | positive integer | Relative probability for choosing this destination. |
Unlike TcpConnector, UdpConnector destination objects do not have per-entry
socket-option overrides in the current implementation. Options such as
large-send-buffer, interface, source-ip, and domain-strategy are
configured at the top level of settings.
The parser also accepts the legacy misspelled key adresses, but new
configurations should use addresses. Do not define both names at the same
time.
Balance Mode
balance-mode controls when weighted destination selection happens.
| Value | Behavior |
|---|---|
"connection" | Default. Choose one weighted destination during upstream init; all payloads on that WaterWall line use that target. |
"packet" | Choose a weighted destination for each upstream payload packet before sending it. |
balance-mode is optional and defaults to "connection".
In "packet" mode, UdpConnector still uses one UDP socket per WaterWall line.
All selected destinations must be compatible with that socket's address family.
For example, avoid mixing IPv4-only and IPv6-only targets in one packet-balanced
list unless the selected socket family can send to all of them.
Optional Settings
| Setting | Type | Default | Description |
|---|---|---|---|
balance-mode | string | "connection" | Weighted destination selection mode: "connection" or "packet". |
large-send-buffer | boolean or positive integer | true | Sets SO_SNDBUF on created UDP sockets. |
large-recv-buffer | boolean or positive integer | true | Sets SO_RCVBUF on created UDP sockets. |
fwmark | integer | not set | Applies a Linux-style socket mark through SO_MARK where supported. |
interface | string | not set | Restricts outbound sockets to a local network device where supported. |
source-ip | string | not set | Binds outbound sockets to a specific local source IP with an ephemeral source port. |
domain-strategy | string or integer | core dns.domain-strategy | Selects how DNS results are chosen for domain destinations. |
Socket Buffer Options
large-send-buffer and large-recv-buffer accept:
| Value | Meaning |
|---|---|
true | Use WaterWall's default large socket buffer size, currently 4194304 bytes. |
false | Do not explicitly set this socket buffer; use the kernel default. |
| positive integer | Request that many bytes directly. |
Both options default to WaterWall's large socket buffer size for UDP connector sockets.
Domain Strategy
domain-strategy controls how a domain destination is converted to an IP
address when DNS returns IPv4 and IPv6 results.
If omitted, the node uses the core dns.domain-strategy value. If the core
value is also omitted, WaterWall defaults to "prefer-ipv4".
Valid string values:
| Value | Behavior |
|---|---|
"prefer-ipv4" | Choose the first IPv4 result if available; otherwise fall back to IPv6. |
"prefer-ipv6" | Choose the first IPv6 result if available; otherwise fall back to IPv4. |
"only-ipv4" | Use only IPv4 DNS results. If none are returned, resolution is unusable for that line. |
"only-ipv6" | Use only IPv6 DNS results. If none are returned, resolution is unusable for that line. |
"accept-dns-returned-order" | Use the first usable address in the resolver's returned order. |
Legacy integer values are also accepted:
| Value | Strategy |
|---|---|
0 | accept-dns-returned-order |
1 | prefer-ipv4 |
2 | prefer-ipv6 |
3 | only-ipv4 |
4 | only-ipv6 |
Interface, Source IP, and Egress Pinning
interface restricts the UDP socket to a local device.
On Linux, WaterWall uses SO_BINDTODEVICE when available. On platforms without
device binding, it falls back to binding the socket to the interface's IPv4
address. If that fallback cannot find an interface IP, socket setup fails.
source-ip binds the UDP socket to a specific local source IP with source port
0, which asks the OS for an ephemeral port. The address family must match the
selected destination family.
If TunDevice loop protection has published an automatic egress pin and
interface is omitted, UdpConnector applies that pin. In that setup,
source-ip alone does not override the pinned interface; make sure the source IP
belongs to the pinned/default interface, or set interface explicitly.
Common Examples
Fixed DNS Destination
{
"name": "dns-out",
"type": "UdpConnector",
"settings": {
"address": "1.1.1.1",
"port": 53
}
}
Context-Based Destination
{
"name": "context-udp-out",
"type": "UdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port"
}
}
This is useful after nodes that fill or rewrite routing context dynamically.
Random Destination Port
{
"name": "random-port-out",
"type": "UdpConnector",
"settings": {
"address": "203.0.113.10",
"port": "random(40000,40100)"
}
}
In "connection" mode, the random port is chosen once during line
initialization and reused for the life of that line.
Packet-Balanced DNS Pool
{
"name": "packet-balanced-dns",
"type": "UdpConnector",
"settings": {
"balance-mode": "packet",
"addresses": [
{
"address": "1.1.1.1",
"port": 53,
"weight": 3
},
{
"address": "8.8.8.8",
"port": 53,
"weight": 1
}
]
}
}
Socket Setup and Lifecycle
During upstream init, UdpConnector:
- Chooses the destination address and port for the line.
- Sets
domain-strategyon the line destination context. - Resolves the domain through its internal
DomainResolverif needed. - Creates a UDP socket after the destination is an IP address.
- Applies send and receive socket buffers.
- Applies optional
interface,fwmark, and egress pin settings. - Binds to
source-ip:0whensource-ipis configured, otherwise to the wildcard address for the selected address family. - Stores the selected destination as the socket peer address.
- Starts reading from the socket and sends downstream
estto the previous node.
UDP has no connection handshake here. est means the local UDP socket is ready,
not that the remote peer has replied.
Domain Resolution
Connection-init DNS resolution is asynchronous. Payloads that arrive before DNS completes are kept in the resolver's bounded pending queue. If resolution fails, the line is finished immediately.
In "packet" balance mode, domain names inside weighted destination objects are
resolved lazily per destination object on each WaterWall line:
- the first packet that selects an unresolved domain starts one async DNS request for that destination object
- packets for that unresolved destination wait in a bounded queue
- after resolution succeeds, the resolved address is reused for later packets on the same line
- there is no time-based DNS cache and no per-packet DNS request
Data Flow
Previous node to remote peer:
upstream payload -> UdpConnector -> UDP send
Remote peer to previous node:
UDP receive -> UdpConnector -> downstream payload
In "connection" mode, inbound datagrams are accepted only from the single
selected remote peer. Datagrams from unexpected peers are ignored.
In "packet" mode, datagrams received on the connector socket are accepted so
replies from any packet-balanced target can return even if replies arrive out of
order.
Flow Control and Buffering
While DNS or packet-destination resolution is pending, UdpConnector may queue
upstream payloads.
Current queue thresholds:
| Queue state | Behavior |
|---|---|
Above 1 KB queued | Pause the previous node. |
| Pending writes complete | Resume the previous node. |
Above 16 MB queued | Close the UDP line. |
When reads are paused, UdpConnector does not queue received datagrams. A
datagram that arrives while reads are paused is dropped.
Idle Timeout
Each UDP line is tracked in an idle table.
Current timeouts:
| State | Timeout |
|---|---|
| After initialization | about 30 seconds |
| After continuing traffic | about 300 seconds |
If the UDP line expires, the socket is closed and downstream finish is sent to
the previous node.
Notes and Caveats
UdpConnectoris an outbound chain end and does not neednext.- DNS resolution is asynchronous and uses the selected
domain-strategy. - Destination objects under
addressesonly containaddress,port, andweight; socket options are top-level settings. fwmarkand device binding are platform-dependent.fwmarkis not available on Windows.- Paused reads drop inbound datagrams instead of buffering them.
- For packet-level, stateless UDP behavior,
UdpStatelessSocketmay be a better fit thanUdpConnector.