Functional Weave
Code in TypeScript

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.

  1. isPort (value: int) -> bool
  2. 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
valueinttrue for 1 to 65535
returnsbool

For example

  • isPort(22) → true 22 is a port
  • isPort(1) → true 1 is the lowest port
  • isPort(65,535) → true 65535 is the highest port
import { isPort } from "#fune/net.port@^1";
impl/typescript/is_port.ts · 8 lines · open · raw
/**
 * 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
portint1 to 65535
protocolProtocol
returnsPortService

For example

  • portService(22, tcp) → port 22, protocol tcp, range system, service ssh, description The Secure Shell (SSH) Protocol 22/tcp is ssh
  • portService(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";
impl/typescript/port_service.ts · 18 lines · open · raw

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
Download for TypeScript net.port-1.0.0-typescript.fune · 29,315 bytes sha256 eb5531b0a6d4083f6588e055634802e7b180eff285220bca89a1e18f4cea657c

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

CaseArgumentsExpected
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

CaseArgumentsExpected
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
CaseArgumentsExpected
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

PathBytes
README.md2,332
data/services.json14,062
impl/python/is_port.py334
impl/python/port_service.py1,261
impl/rust/is_port.rs641
impl/rust/port_service.rs2,078
impl/typescript/is_port.ts351
impl/typescript/port_service.ts1,190
vectors.json4,830