net.port
Check a TCP/UDP port number and name the IANA service registered on it, with its system/user/dynamic range.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 30 tests, run in TypeScript, Python and Rust.isPort 9 · portService 21
What it does
Two functions for TCP and UDP port numbers:
- `isPort(value)` answers whether a number is a port you can connect to: a whole number from 1 to 65535. It never throws, so it is the check to use when validating input. - `portService(port, protocol)` says which RFC 6335 range the port is in and which service IANA registered on it for `tcp` or `udp`. It throws on a port `isPort` refuses or an unknown protocol.
The functions
A group: 2 functions that work together, each in its own file, each pinned by its own tests in TypeScript, Python and Rust. A project can install only the ones it calls.
- isPort (value: int) -> bool
- portService (port: int, protocol: Protocol) -> PortService
The types it declares, generated into your project
export type Protocol = "tcp" | "udp";
export type PortRange = "system" | "user" | "dynamic";
/** A port, the range RFC 6335 puts it in, and the service registered on it. */
export interface PortService {
readonly port: number;
readonly protocol: Protocol;
readonly range: PortRange;
/** IANA service name, null when this registry has none for it */
readonly service: string | null;
readonly description: string | null;
}
Once installed, your code imports each one from the group's module.
isPort 9 tests
export function isPort(value: number): boolean
| value | int | true for 1 to 65535 |
| returns | bool |
For example
isPort(22)→ true 22 is a portisPort(1)→ true 1 is the lowest portisPort(65,535)→ true 65535 is the highest port
import { isPort } from "#fune/net.port@^1";
/**
* Whether a number is a usable TCP or UDP port: a whole number from 1 to
* 65535. Port 0 is reserved (RFC 6335) and asks the operating system to pick
* one, so it is never a port to connect to.
*/
export function isPort(value: number): boolean {
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
}portService throws on bad input 21 tests
export function portService(port: number, protocol: Protocol): PortService
| port | int | 1 to 65535 |
| protocol | Protocol | |
| returns | PortService |
For example
portService(22, tcp)→ port 22, protocol tcp, range system, service ssh, description The Secure Shell (SSH) Protocol 22/tcp is sshportService(53, udp)→ port 53, protocol udp, range system, service domain, description Domain Name Server 53/udp is domain (DNS)portService(80, tcp)→ port 80, protocol tcp, range system, service http, description World Wide Web HTTP 80/tcp is http, the primary of IANA's three names for it
import { portService } from "#fune/net.port@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { isPort } from "./net_port_is_port.ts"; ← isPort, another function of this group · built into the same file, even by a slim install
import { SERVICES } from "./net_port_data.ts"; ← this capability’s own data, compiled from data/services.json into the same file by fune build
import { type PortRange, type PortService, type Protocol } from "./net_port_types.ts";
/**
* The RFC 6335 range a port is in, and the IANA service registered on it for
* a protocol. A port with nothing in this registry's table has a null service:
* unassigned, or assigned but not common enough to ship here.
*/
export function portService(port: number, protocol: Protocol): PortService {
if (typeof port !== "number" || !Number.isInteger(port)) throw new TypeError(`port must be an integer, received ${port}`);
if (!isPort(port)) throw new RangeError(`port must be 1 to 65535, received ${port}`);
if (protocol !== "tcp" && protocol !== "udp") throw new RangeError(`protocol must be tcp or udp, received "${protocol}"`);
// RFC 6335 section 6: system 0-1023, user 1024-49151, dynamic 49152-65535.
const range: PortRange = port <= 1023 ? "system" : port <= 49151 ? "user" : "dynamic";
const row = SERVICES.find((s) => s.port === port && s.protocol === protocol);
return { port, protocol, range, service: row ? row.service : null, description: row ? row.description : null };
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add net.port
That builds the whole group. To build only what you call, and whatever it uses inside the group:
fune add net.port --only isPort
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./net.port-1.0.0-typescript.fune, or fetch it from a terminal with fune pull net.port@1.0.0:typescript.
The whole function, every language, is one file too: net.port-1.0.0.fune, 33,904 bytes, sha256 b4abf0a4585a74e0ed2cb54fce546dc137d24a263d490d0705b908921f46fd33. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before net.port.isPort
// fune: before net.port.portService
after — your function gets the result and the arguments, and returns the final result.
// fune: after net.port.isPort
// fune: after net.port.portService
replace — it requires no other capability, so there is no dependency to replace.
step — your function runs at a numbered point inside a function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show net.port --steps.
// fune: step net.port.<fn> after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
isPort 9 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 22 is a port | 22 | → | true |
| 1 is the lowest port | 1 | → | true |
| 65535 is the highest port | 65,535 | → | true |
| 0 is reserved, not a port to connect to | 0 | → | false |
| 65536 does not fit in 16 bits | 65,536 | → | false |
| negative numbers are not ports | -80 | → | false |
| a fraction is not a port | 80.5 | → | false |
| a string of digits is not a number | 80 | → | false |
| null is not a port | — | → | false |
portService 21 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 22/tcp is ssh | 22, tcp | → | port 22, protocol tcp, range system, service ssh, description The Secure Shell (SSH) Protocol |
| 53/udp is domain (DNS) | 53, udp | → | port 53, protocol udp, range system, service domain, description Domain Name Server |
| 80/tcp is http, the primary of IANA's three names for it | 80, tcp | → | port 80, protocol tcp, range system, service http, description World Wide Web HTTP |
| 443/tcp is https | 443, tcp | → | port 443, protocol tcp, range system, service https, description http protocol over TLS/SSL |
| 1023 is the last system port | 1,023, tcp | → | port 1,023, protocol tcp, range system, service —, description — |
| 1024 is the first user port, and is reserved | 1,024, tcp | → | port 1,024, protocol tcp, range user, service —, description — |
| 5432/tcp is postgresql | 5,432, tcp | → | port 5,432, protocol tcp, range user, service postgresql, description PostgreSQL Database |
| 5433/tcp is registered to Pyrrho, not a second PostgreSQL | 5,433, tcp | → | port 5,433, protocol tcp, range user, service pyrrho, description Pyrrho DBMS |
| 6379/tcp is redis | 6,379, tcp | → | port 6,379, protocol tcp, range user, service redis, description An advanced key-value cache and store |
| 6379/udp is reserved, so no service | 6,379, udp | → | port 6,379, protocol udp, range user, service —, description — |
Show the other 11 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 4874 is unassigned (IANA 4872-4875) | 4,874, tcp | → | port 4,874, protocol tcp, range user, service —, description — |
| 8080/tcp is http-alt | 8,080, tcp | → | port 8,080, protocol tcp, range user, service http-alt, description HTTP Alternate (see port 80) |
| 465/tcp is submissions (RFC 8314), not the retired urd | 465, tcp | → | port 465, protocol tcp, range system, service submissions, description Message Submission over TLS protocol |
| 49151 is the last user port | 49,151, udp | → | port 49,151, protocol udp, range user, service —, description — |
| 49152 is the first dynamic port | 49,152, tcp | → | port 49,152, protocol tcp, range dynamic, service —, description — |
| 65535 is dynamic | 65,535, udp | → | port 65,535, protocol udp, range dynamic, service —, description — |
| port 0 is refused | 0, tcp | → | error: port must be 1 to 65535, received 0 |
| port 65536 is refused | 65,536, tcp | → | error: port must be 1 to 65535, received 65536 |
| a fractional port is refused | 22.5, tcp | → | error: port must be an integer |
| a protocol other than tcp or udp is refused | 22, sctp | → | error: protocol must be tcp or udp, received "sctp" |
| protocol is case-sensitive | 22, TCP | → | error: protocol must be tcp or udp, received "TCP" |
More from the author
They are a group because `portService` is only defined on what `isPort` accepts; a caller validating an inventory wants the first, one labelling a report wants the second.
## Ranges (RFC 6335 section 6)
| range | ports | | |-------|-------|--| | `system` | 0-1023 | assigned by IETF review; binding needs privilege on Unix | | `user` | 1024-49151 | assigned by IANA on request | | `dynamic` | 49152-65535 | never assigned; ephemeral client ports |
Port 0 is reserved and means "let the OS pick", so `isPort(0)` is false.
## The service table
`data/services.json` holds 144 rows (76 ports, tcp and udp) copied verbatim from the IANA Service Name and Transport Protocol Port Number Registry: the service name and description exactly as IANA writes them, including its capitalisation. It is a subset: the ports an operations team meets (ssh, dns, http/https, mail, directory, databases, message brokers, remote access, VPN). A port not in the table has `service: null`, which means "not in this table", not necessarily "unassigned".
Choices where IANA lists more than one name on a port/protocol:
- 80: `http` (IANA also lists `www` and `www-http` as aliases). - 465/tcp: `submissions` (RFC 8314), not the historical `urd`. - 2049: `nfs`, not `shilp`. - Everywhere else, the first name IANA lists. Rows IANA marks `Reserved` (e.g. 6379/udp, 993/udp) are left out, so they have no service.
Notable answers: 5433 is registered to `pyrrho` (Pyrrho DBMS), not to a second PostgreSQL, however often Postgres is run there; 4874 is unassigned (IANA lists 4872-4875 as Unassigned).
Source: IANA, "Service Name and Transport Protocol Port Number Registry", https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.csv, fetched 2026-09-26. RFC 6335 (Procedures for the Management of the Service Name and Transport Protocol Port Number Registry), section 6, for the ranges.
Files
| Path | Bytes |
|---|---|
| README.md | 2,332 |
| data/services.json | 14,062 |
| impl/python/is_port.py | 334 |
| impl/python/port_service.py | 1,261 |
| impl/rust/is_port.rs | 641 |
| impl/rust/port_service.rs | 2,078 |
| impl/typescript/is_port.ts | 351 |
| impl/typescript/port_service.ts | 1,190 |
| vectors.json | 4,830 |