Skip to main content

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:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "UdpConnector".
settingsobjectDestination and socket configuration.

settings must use exactly one destination style:

StyleRequired fieldsDescription
Single destinationaddress and portEvery line uses the same destination rule.
Weighted destinationsaddressesEach 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:

ValueBehavior
IPv4 stringSend to that IPv4 address.
IPv6 stringSend to that IPv6 address.
Domain stringResolve 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:

ValueBehavior
numberUse that constant port. Must be 1 through 65535.
numeric stringUse 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:

FieldTypeDescription
addressstringSame supported forms as single-destination address.
portnumber or stringSame supported forms as single-destination port, including random(x,y).
weightpositive integerRelative 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.

ValueBehavior
"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

SettingTypeDefaultDescription
balance-modestring"connection"Weighted destination selection mode: "connection" or "packet".
large-send-bufferboolean or positive integertrueSets SO_SNDBUF on created UDP sockets.
large-recv-bufferboolean or positive integertrueSets SO_RCVBUF on created UDP sockets.
fwmarkintegernot setApplies a Linux-style socket mark through SO_MARK where supported.
interfacestringnot setRestricts outbound sockets to a local network device where supported.
source-ipstringnot setBinds outbound sockets to a specific local source IP with an ephemeral source port.
domain-strategystring or integercore dns.domain-strategySelects how DNS results are chosen for domain destinations.

Socket Buffer Options

large-send-buffer and large-recv-buffer accept:

ValueMeaning
trueUse WaterWall's default large socket buffer size, currently 4194304 bytes.
falseDo not explicitly set this socket buffer; use the kernel default.
positive integerRequest 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:

ValueBehavior
"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:

ValueStrategy
0accept-dns-returned-order
1prefer-ipv4
2prefer-ipv6
3only-ipv4
4only-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:

  1. Chooses the destination address and port for the line.
  2. Sets domain-strategy on the line destination context.
  3. Resolves the domain through its internal DomainResolver if needed.
  4. Creates a UDP socket after the destination is an IP address.
  5. Applies send and receive socket buffers.
  6. Applies optional interface, fwmark, and egress pin settings.
  7. Binds to source-ip:0 when source-ip is configured, otherwise to the wildcard address for the selected address family.
  8. Stores the selected destination as the socket peer address.
  9. Starts reading from the socket and sends downstream est to 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 stateBehavior
Above 1 KB queuedPause the previous node.
Pending writes completeResume the previous node.
Above 16 MB queuedClose 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:

StateTimeout
After initializationabout 30 seconds
After continuing trafficabout 300 seconds

If the UDP line expires, the socket is closed and downstream finish is sent to the previous node.

Notes and Caveats

  • UdpConnector is an outbound chain end and does not need next.
  • DNS resolution is asynchronous and uses the selected domain-strategy.
  • Destination objects under addresses only contain address, port, and weight; socket options are top-level settings.
  • fwmark and device binding are platform-dependent.
  • fwmark is not available on Windows.
  • Paused reads drop inbound datagrams instead of buffering them.
  • For packet-level, stateless UDP behavior, UdpStatelessSocket may be a better fit than UdpConnector.