Developers

Socket

7 min readView as markdown ↗

Use an SDK socket to connect your application's TCP or UDP client through a URnetwork Device. It has familiar reads, writes, deadlines, and close behavior, while its traffic follows the Device's selected connection path. You can use it for one client without routing the rest of the machine through an operating-system VPN.

Start with an initialized Device from Getting started with the SDK. Socket methods are available in the updated SDK source and require matching socket support on a remote Device. Use SDK builds containing these APIs on both sides of an RPC connection.

Use Install for native package-manager commands and Examples for runnable socket and HTTP-library integrations in twelve languages, including separate JavaScript Node and browser programs.

How it differs from a kernel socket

A kernel socket belongs to the operating system's network stack. An SDK socket belongs to the Device and uses an in-process TCP/IP stack. The Device sends its packets over its configured route, including its selected provider when connected through URnetwork.

Kernel socketSDK socket
Network pathOS routing table and interface selectionDevice routing, provider selection, and policy
Application interfaceFile descriptor or platform socket objectGo net.Conn, JS Conn, C handle, or mobile Socket
ScopeApplication connection on the host networkApplication connection owned by one Device
System VPN needed for this connectionDepends on how the OS route is configuredNo OS TUN/VPN interface is required solely for SDK socket calls
Local addressHost interface/address spaceVirtual address in the Device's user-space stack
Closing the DeviceNo relationshipCloses its SDK connections
Socket options and listenersPlatform-dependent OS APIsOutbound connections only; no file descriptor, arbitrary socket options, or listen API

The virtual localAddr is not the provider's public exit IP, and binding to it does not make your app reachable from the Internet. The underlying URnetwork connection and provider can still use operating-system networking. An SDK socket does not change the Device's configured routing mode or create a provider connection by itself.

TLS and DTLS encrypt the application connection to its destination. Plain TCP and UDP preserve the application's plaintext protocol; URnetwork's transport encryption to a provider does not turn that protocol into destination TLS.

Networks and hostnames

Dial a host:port using one of these networks:

NetworkBehavior
tcpTCP with IPv4/IPv6 Happy Eyeballs for a hostname
tcp4, tcp6TCP restricted to that address family
udpUDP; a dual-family hostname selects its peer by racing the first datagram/reply
udp4, udp6UDP restricted to that address family

Bracket IPv6 literals, for example [2001:db8::10]:443. Hostname resolution uses the Device's socket resolver and packet path. It does not fall back to resolving or opening the destination directly through the host when a provider is unavailable.

TCP Happy Eyeballs races connection attempts. TLS/DTLS dialers select a family after its secure handshake succeeds. Explicit family names and literal addresses bypass the dual-family race.

UDP: possible duplicate delivery

UDP has no connection handshake. For udp with both A and AAAA records, the first write goes to IPv6, then to IPv4 after 250 ms without a reply. An immediate IPv6 write failure starts the fallback sooner. The initial application datagram may reach both addresses. The first reply selects the peer, and later writes go only to that peer.

Use request IDs or another application-level duplicate-handling mechanism when duplicate delivery matters. A successful first write does not prove delivery or select a peer. If the server never replies, later writes wait for selection until their deadline or close. For send-only protocols, use udp4, udp6, or a literal IP. A zero-byte reply is a valid reply.

Each UDP write is one datagram. Each read consumes one datagram; a small read buffer discards the remaining bytes. An empty datagram is data, not EOF. Keep messages within the limits of the destination protocol and path.

Go

DeviceLocal and DeviceRemote offer Dial, DialContext, DialTls, and DialTlsContext. Ordinary dial methods use the same signatures as Go's net.Dialer, and return a net.Conn-compatible connection.

// device is an initialized sdk.Device.
conn, err := device.DialContext(ctx, "tcp", "example.com:80")
if err != nil {
    return err
}
defer conn.Close()
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
    return err
}
_, err = conn.Write([]byte("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"))

An existing HTTP client can use the Device directly:

transport := &http.Transport{DialContext: device.DialContext}
defer transport.CloseIdleConnections()
client := &http.Client{Transport: transport, Timeout: 20 * time.Second}
// net/http performs normal HTTPS certificate verification above this dialer.

For direct encryption, call device.DialTlsContext(ctx, "tcp", "example.com:443", nil). TCP uses TLS; UDP uses DTLS 1.2. A nil TLS configuration uses normal certificate verification. The original hostname supplies the default server name. DTLS supports a smaller configuration surface than Go TLS and rejects unsupported security options.

Reads and writes may return partial data with an error; process the returned bytes. A dial context controls connection establishment, not the lifetime of a successful connection. Set absolute deadlines for subsequent I/O and use time.Time{} to clear them. Close the connection when finished. The default establishment/secure-handshake bound is 30 seconds, shortened by an earlier caller deadline.

JavaScript and TypeScript

SDK Device wrappers provide Promise-based dial and dialTls methods. A browser page uses a configured remote Device that supports sockets. The SDK exposes the remote connection through WASM; it does not require native browser Direct Sockets support or Isolated Web App packaging.

const conn = await device.dialTls("tcp", "example.com:443", undefined, {
  timeoutMillis: 5000,
});
try {
  await conn.setDeadline(Date.now() + 5000);
  await conn.write(new TextEncoder().encode(
    "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"
  ));
  for (let chunk; (chunk = await conn.read()) !== null;) {
    console.log(new TextDecoder().decode(chunk));
  }
} finally {
  await conn.close();
}

read() returns a Uint8Array, or null at EOF. An empty array is a valid UDP datagram. readable and writable provide Web Streams adapters; choose either the direct methods or the adapters for each direction. TCP stream close sends a write-side FIN when supported. Explicit close() closes the whole connection.

Use an AbortSignal to cancel opening. Established sockets use deadlines and close. Deadline methods accept epoch milliseconds, a Date, or null to clear. A partial write error includes bytesWritten; partial read data is returned before its accompanying error on the next direct read. Socket traffic stops when its owning remote service disconnects; it is not replayed after reconnection.

Direct Sockets API

Use the Direct Sockets constructor signatures with a UR Device:

const {TCPSocket, UDPSocket} = device.directSockets;
const socket = new TCPSocket("echo.example", 9000);
const {readable, writable} = await socket.opened;
const reader = readable.getReader({mode: "byob"});
const writer = writable.getWriter();
try {
  await writer.write(new TextEncoder().encode("hello"));
  await writer.close();
  console.log(await reader.read(new Uint8Array(1024)));
} finally {
  await Promise.allSettled([reader.cancel(), writer.abort()]);
  reader.releaseLock(); writer.releaseLock();
  await socket.close();
  await socket.closed;
}

For UDP, construct new UDPSocket({remoteAddress: "echo.example", remotePort: 9001}). Await opened, then write {data: new Uint8Array([1, 2, 3])} and read value.data. Each object is one datagram, including a zero-byte datagram. Connected UDP messages omit remote-address fields and reject per-message destination overrides.

The exported createDirectSockets(device) factory also returns these constructors. Both interfaces have opened and closed promises and asynchronous close(). TCP supports default and BYOB readers and BufferSource writes. Closing its writable stream sends FIN while reads remain available. Cancel/abort pending operations and release reader/writer locks before closing a socket; otherwise close() rejects with InvalidStateError. Network failures reject with NetworkError.

Chrome's native API is available to Isolated Web Apps. The SDK implementation works in ordinary browsers and Node over the configured Device, with no IWA package or native Direct Sockets permission. It does not replace browser globals. See the Chrome documentation and Direct Sockets proposal.

This release implements outbound TCP and connected UDP. Use dnsQueryType: "ipv4" or "ipv6" to restrict resolution; omission preserves Device Happy Eyeballs. Bound UDP, multicast, listeners, and per-socket buffer/no-delay/keep-alive tuning are unavailable and valid requests for those features fail with NotSupportedError. This is a client compatibility profile, not the complete browser API.

For dual-family UDP names, opened resolves before sending. Its endpoint fields initially describe the first candidate and update after consuming the first reply. The initial datagram can reach both addresses. Choose a literal IP or a DNS family when a fixed endpoint is needed at opening. The examples use a timer to cancel stalled stream I/O; the separate Conn API also provides deadlines.

Direct Sockets constructors open plain TCP/UDP connections. Use dialTls for TLS/DTLS. The runnable JavaScript Node/browser examples and TypeScript example include TCP/UDP echo, timeouts, cleanup, and HTTP-library integration.

Native bindings and supported platforms

BindingSocket surface and platform
Gonet.Conn behavior on the SDK's supported Go targets
Android Kotlin/JavaPortable OpenSocket/Socket in the AAR; Android API 24+ with the shipped ABIs
Apple SwiftPortable OpenSocket/Socket in the XCFramework; iOS 16+ and macOS 13.5+ with the shipped device/simulator slices
C/C++ and other FFI clientsC ABI for Windows 10+ and Linux glibc 2.35+ on amd64/arm64; macOS host builds for development
Browser and Node JS/TSWASM, Conn and Direct Sockets Web Streams, plus an available socket-capable Device RPC endpoint

Mobile callers use OpenSocket(network, address, timeoutMillis, tlsOptions). Nil TLS options request a plain connection; a nonnil configuration requests TLS/DTLS. Socket.Read returns a result with Data and Eof; deadline methods take epoch milliseconds, with zero to clear. Use a worker thread or appropriate coroutine dispatcher for blocking operations.

C callers use urnet_device_dial / urnet_device_dial_tls, followed by urnet_conn_read, urnet_conn_write, and the deadline/close functions. Read returns a byte count and a separate EOF flag. It consumes data immediately; it is not an output-buffer size query. Process partial byte counts even when an error string is returned. Free error/address strings with urnet_free_string, and release the connection handle with urnet_release, which also closes it.

The SDK currently provides client connections. Server/listener sockets will be a separate addition with explicit reachability and ownership semantics. See the SDK tour for the binding/build model and Getting started for Device setup.