7.2.1.2. Reuse last port operation

This tutorial explains how to implement the anj_udp_reuse_last_port function to support Queue Mode in Anjay Lite. In LwM2M Queue Mode, the client may temporarily disconnect and enter a low-power mode. To maintain the session and allow the server to resume communication when the client reconnects, the client must reuse the same local port number across connections. If the client changes the port, the server may treat it as a new device, disrupting the ongoing session.

Note

Reusing the same port is also important when operating behind a NAT (Network Address Translation) device. NAT devices map external connections to the internal IP address and port of the client. If the client changes its local port, the NAT mapping may be lost, and the LwM2M server may no longer be able to reach the device after it reconnects. Maintaining a consistent local port helps preserve the NAT binding and ensures reliable communication.

However, NAT entries may expire after periods of inactivity. Even with port reuse enabled, the NAT device may assign a different external port. This behavior depends on the NAT implementation and is beyond the client’s control.

Note

This tutorial builds upon the Minimal Socket Implementation. The complete source code for this example can be found in the examples/custom-network/reuse-port directory.

The anj_udp_reuse_last_port function is called by Anjay Lite after closing an existing connection and before establishing a new one. If the socket context has not established any previous connection, the function returns an error.

You can find more information about Queue Mode and how to enable it in Anjay Lite here.

7.2.1.2.1. Prepare socket binding for port reuse

Update the network compatibility layer to support binding the socket to a specific port. This is necessary for reusing the same port across multiple connections, which is especially important for enabling Queue Mode.

Extend the net_ctx_posix_impl_t structure to store the last local port used by the client:

typedef struct net_ctx_posix_impl {
    sockfd_t sockfd;
    anj_net_socket_state_t state;
    bool local_port_was_set;
    uint16_t port; // client side connection port number in network order
} net_ctx_posix_impl_t;

How it works

  • local_port_was_set tracks whether a port has been assigned.

  • port stores the last used local port number.

These fields will later be used during socket creation to attempt binding to the same port when reconnecting.

Add two helper functions: store_local_port_in_ctx and create_net_socket.

static void store_local_port_in_ctx(net_ctx_posix_impl_t *ctx) {
    struct sockaddr addr;
    socklen_t addrlen = sizeof(addr);

    ctx->local_port_was_set = false;

    if (getsockname(ctx->sockfd, &addr, &addrlen)) {
        return;
    }

    /* assume IPv4 address family */
    struct sockaddr_in *addr_in = (struct sockaddr_in *) &addr;
    ctx->port = addr_in->sin_port;
    ctx->local_port_was_set = true;
}

static int create_net_socket(net_ctx_posix_impl_t *ctx) {
    ctx->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (ctx->sockfd < 0) {
        return NET_GENERAL_ERROR;
    }

    /* Always allow for reuse of address */
    int reuse_addr = 1;
    if (setsockopt(ctx->sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,
                   sizeof(reuse_addr))) {
        close(ctx->sockfd);
        ctx->sockfd = INVALID_SOCKET;
        return NET_GENERAL_ERROR;
    }

    return ANJ_NET_OK;
}

How it works

  • store_local_port_in_ctx() saves the assigned local port after a successful connection.

  • create_net_socket() uses the socket() function to create a new socket descriptor. It also ensures that the socket allows address reuse using the SO_REUSEADDR option. This allows the operating system to bind to a recently used ephemeral port, even if it hasn’t fully released it yet ensuring smoother reconnections and better compatibility across different platforms.

Enhance the anj_udp_connect function to integrate the create_net_socket and store_local_port_in_ctx helper functions.

if (ctx->sockfd == INVALID_SOCKET) {
    if (create_net_socket(ctx)) {
        freeaddrinfo(serverinfo);
        return NET_GENERAL_ERROR;
    }
}

if (connect(ctx->sockfd, serverinfo->ai_addr, serverinfo->ai_addrlen)) {
    freeaddrinfo(serverinfo);
    return NET_GENERAL_ERROR;
}
set_socket_non_blocking(ctx->sockfd);
ctx->state = ANJ_NET_SOCKET_STATE_CONNECTED;

store_local_port_in_ctx(ctx);

How it works

  • If the current socket is invalid (INVALID_SOCKET), it creates a new one using create_net_socket.

  • After establishing a connection, it saves the assigned local port using store_local_port_in_ctx. This ensures the client can reuse the same port during future reconnections.

7.2.1.2.2. Implement port reuse

We are now ready to implement the anj_udp_reuse_last_port function.

Define a helper function net_bind_to_local_port that handles the socket creation and bind operation:

static int net_bind_to_local_port(net_ctx_posix_impl_t *ctx,
                                  struct addrinfo **serverinfo,
                                  const uint16_t port_in_net_order) {
    char port[ANJ_U16_STR_MAX_LEN + 1];
    if (!anj_uint16_to_string_value(port, ntohs(port_in_net_order))) {
        return NET_GENERAL_ERROR;
    }

    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;

    if (getaddrinfo("0.0.0.0", port, &hints, serverinfo)) {
        return NET_GENERAL_ERROR;
    }

    // Use the first entry in serverinfo
    struct addrinfo *local_addr = *serverinfo;
    if (!local_addr) {
        return NET_GENERAL_ERROR;
    }

    if (create_net_socket(ctx)) {
        return NET_GENERAL_ERROR;
    }

    if (bind(ctx->sockfd, local_addr->ai_addr, local_addr->ai_addrlen) < 0) {
        return NET_GENERAL_ERROR;
    }

    ctx->state = ANJ_NET_SOCKET_STATE_BOUND;
    return ANJ_NET_OK;
}

How it works

  • It calls getaddrinfo() with the wildcard IPv4 address (0.0.0.0) and the port number we wish to use on the device side for the connection. This prepares a local address that can be used for binding.

  • After creating a new socket, it binds the socket to the first address returned in the serverinfo structure. If the bind operation succeeds, the socket state is updated to ANJ_NET_SOCKET_STATE_BOUND.

Define the anj_udp_reuse_last_port function itself:

int anj_udp_reuse_last_port(anj_net_ctx_t *ctx_) {
    net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) ctx_;
    if (!ctx->local_port_was_set) {
        return NET_GENERAL_ERROR;
    }

    struct addrinfo *serverinfo = NULL;
    int ret = net_bind_to_local_port(ctx, &serverinfo, ctx->port);

    if (serverinfo) {
        freeaddrinfo(serverinfo);
    }

    return ret;
}

How it works

  • Checks if a previous local port is available.

  • Binds the socket to the stored port for reuse.

7.2.1.2.3. Summary

After completing this implementation, Anjay Lite will:

  • establish a connection with the LwM2M server,

  • enter Queue Mode after 5 seconds (as configured),

  • reuse the same local port for all subsequent connections.

By reusing the local port, the client can send an Update message directly after awaking from Queue Mode, without needing to perform a full registration. This ensures session continuity and more efficient communication with the LwM2M server.