Port allocation

Many web application modules need a TCP or UDP port to run a backend exposed by Traefik. Such modules can set the org.nethserver.tcp-ports-demand and org.nethserver.udp-ports-demand which takes an integer number as value. Example:

org.nethserver.tcp-ports-demand=1
org.nethserver.udp-ports-demand=1

The randomly-allocated TCP port number will be available inside the TCP_PORT environment variable and it will be available to all step scripts and inside systemd units. The available environment variables will be:

  • TCP_PORT, UDP_PORT: it is always present and it contains always the first port, i.e. 20001
  • TCP_PORTS_RANGE, UDP_PORTS_RANGE: only if value is greater than 1, it contains the list of ports in range format, i.e 20001-20002
  • TCP_PORTS, UDP_PORTS: only if value is greater than 1 and less or equal than 8, it contains a comma separated list of ports like, i.e. 20001,20002,20003

Currently, allocated ports are saved in an SQLite database file managed by the local node agent.

Authorizations

The module requires an additional role to manage port allocation, which is assigned by setting the org.nethserver.authorizations label on the module image, as shown in the following example:

org.nethserver.authorizations = node:portsadm

The following additional label values can be used to mix port allocations with other existing node-related roles:

  • org.nethserver.authorizations = node:fwadm,portsadm
  • org.nethserver.authorizations = node:tunadm,portsadm

Note that the value must be exactly one of the above. Other combinations like node:portsadm,fwadm are not valid.

The module will be granted execution permissions for the following actions on the local node:

  • allocate-ports
  • deallocate-ports

These actions can be carried out using the agent library without making direct node API calls, as explained in the next section.

Agent library

The Python agent library provides a convenient interface for managing port allocation and deallocation, based on the node actions allocate_ports and deallocate_ports.

This interface dynamically allocate and deallocate ports based on the module’s needs without requiring direct interaction with the node’s APIs.

It is optional to specify the module_id when calling the port allocation or deallocation functions. By default, if the module_id is not provided, the function will automatically use the value of the MODULE_ID environment variable. This simplifies the function calls in common scenarios, ensuring the correct module name is used without manual input. However, if needed, you can still explicitly pass the module_id. Note that only the cluster agent can modify the port allocations of other modules.

Allocate ports

Imagine an application module that initially requires only one TCP port. Later, a new feature is added, and it needs four TCP ports to handle more connections.

If ports are already allocated for this module, the previous allocation will be deallocated, and the new requested range of ports will be allocated. Here’s how this can be done:

import agent

# Allocate 4 TCP ports for the module that is calling the function
allocated_ports = agent.allocate_ports(4, "tcp")

or

import agent

# Allocate 4 UDP ports for "my_module" module
allocated_ports = agent.allocate_ports(4, "udp", "my_module")

If you want to preserve the previously allocated ports and add a new range of ports, you can use the keep_existing parameter:

import agent

# Allocate 4 more TCP ports for the module that is calling the function
allocated_ports = agent.allocate_ports(4, "tcp", keep_existing=True)

Deallocate ports

If the module no longer needs the allocated ports, such as when a feature is removed or disabled, the ports can be easily deallocated:

import agent

# Deallocate TCP ports for the module that is calling the function
deallocated_port_ranges = agent.deallocate_ports("tcp")

or

import agent

# Deallocate UDP ports for the "my_module" module
deallocated_port_ranges = agent.deallocate_ports("udp", "my_module")

By deallocating the ports, the module frees up the resources, allowing other modules to use those ports.

For more information about the low-level implementation of port allocation, see Port allocation in the core documentation.