Socket
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 socket | SDK socket | |
|---|---|---|
| Network path | OS routing table and interface selection | Device routing, provider selection, and policy |
| Application interface | File descriptor or platform socket object | Go net.Conn, JS Conn, C handle, or mobile Socket |
| Scope | Application connection on the host network | Application connection owned by one Device |
| System VPN needed for this connection | Depends on how the OS route is configured | No OS TUN/VPN interface is required solely for SDK socket calls |
| Local address | Host interface/address space | Virtual address in the Device's user-space stack |
| Closing the Device | No relationship | Closes its SDK connections |
| Socket options and listeners | Platform-dependent OS APIs | Outbound 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:
| Network | Behavior |
|---|---|
tcp | TCP with IPv4/IPv6 Happy Eyeballs for a hostname |
tcp4, tcp6 | TCP restricted to that address family |
udp | UDP; a dual-family hostname selects its peer by racing the first datagram/reply |
udp4, udp6 | UDP 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
| Binding | Socket surface and platform |
|---|---|
| Go | net.Conn behavior on the SDK's supported Go targets |
| Android Kotlin/Java | Portable OpenSocket/Socket in the AAR; Android API 24+ with the shipped ABIs |
| Apple Swift | Portable OpenSocket/Socket in the XCFramework; iOS 16+ and macOS 13.5+ with the shipped device/simulator slices |
| C/C++ and other FFI clients | C ABI for Windows 10+ and Linux glibc 2.35+ on amd64/arm64; macOS host builds for development |
| Browser and Node JS/TS | WASM, 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.