PDN management

The Modem library supports configuring up to 10 packet data network (PDN) sockets. PDN sockets are an extension to the standard socket API. They allow for configuring PDN connections as identified by the user’s Access Point Name (APN).

After a PDN socket is configured, it can be used by a data socket (IP or RAW socket). The purpose of PDN sockets is to dictate where to send the data, or to do DNS queries on the PDN that is associated with the given APN.

The following sections demonstrate pseudo code for typical use cases of PDN sockets.

Creating a PDN socket

pdn_fd = nrf_socket(NRF_AF_LTE, NRF_SOCK_MGMT, NRF_NPROTO_PDN);
nrf_connect(pdn_fd, (struct nrf_sockaddr *)"custom.apn", strlen("custom.apn"));

When creating a PDN socket, the library requests a context ID (CID) to be created. It then configures the CID, which creates a PDN. This PDN is used to handle data that is sent over the connection.

The example code shows how to create a PDN socket with the protocol family NRF_NPROTO_PDN and the proprietary address family NRF_AF_LTE. When the nrf_socket() function returns, a CID has been allocated. Next, the nrf_connect() function is used to configure the created CID to use the application’s APN. If this operation is successful, the library attempts to activate the associated PDN handle. If this activation succeeds, nrf_connect() returns with success.

The file descriptor that is returned can only be used to operate on the PDN socket itself. For example, you can use it to set and retrieve socket options, to connect, or to close the PDN socket. To use the configured PDN, you must use the associated APN name to identify which PDN to use to transfer data (IP/RAW/DNS data). See also Binding a data socket to an APN.

Closing a PDN socket

nrf_close(pdn_fd);

The close function for PDN sockets de-registers the associated CID/PDN by deactivating the PDN. It also removes the configuration of the CID.

Any socket that has been bound to the APN/PDN must be closed before closing the PDN socket. Otherwise, it will not have any transport and will start reporting errors.

Binding a data socket to an APN

The following code shows how to create an IPv4 TCP stream socket and bind it to an APN using the NRF_SO_BINDTODEVICE socket option. The APN name must be set up and activated prior to binding it. See Creating a PDN socket.

tcp_fd = nrf_socket(NRF_AF_INET, NRF_SOCK_STREAM, NRF_IPPROTO_TCP);
nrf_setsockopt(tcp_fd, NRF_SOL_SOCKET, NRF_SO_BINDTODEVICE, "custom.apn", strlen("custom.apn"));
# Data will now flow over "custom.apn" instead of Default context.
nrf_connect(...);
nrf_send(tcp_fd, "data", 4, 0);

Note

If the PDN that the socket is being bound to has been deactivated by the network, setting the NRF_SO_BINDTODEVICE option returns an error stating that the APN parameter is not valid. To fix this error, you must create another PDN socket that is associated with the APN, and bind the data socket to it.

Note

From Modem library v1.1.0, binding a socket to a PDN ID is introduced and binding a socket to an APN is deprecated. It is recommended that the application bind a socket to a PDN ID.

Binding a data socket to a PDN ID

The following code shows how to create an IPv4 TCP stream socket and bind it to a PDN ID:

fd = nrf_socket(NRF_AF_INET, NRF_SOCK_STREAM, NRF_IPPROTO_TCP);
nrf_setsockopt(fd, NRF_SOL_SOCKET, NRF_SO_BINDTODEVICE, "pdn1", strlen("pdn1"));

Configuring the type of transport

nrf_setsockopt(pdn_fd, NRF_SOL_PDN, NRF_SO_PDN_AF, [NRF_AF_INET, NRF_AF_INET6],
               sizeof(nrf_sa_family_t) * 2);

You can configure a PDN socket to use a specific Internet family type of transport, for example, IPv4 only, IPv6 only, or dual stack. To do so, set the socket option NRF_SO_PDN_AF to request a specific type of connection. You must do this after creating the socket and before activating it with nrf_connect().

To retrieve a list of what address families were activated by the network, call nrf_getsockopt() again after activation, using the same socket option.

Limitation

The current implementation is hardcoded to always request a dual IP stack.

Checking if a PDN is active

nrf_pdn_state_t state;
nrf_getsockopt(pdn_fd, NRF_SOL_PDN, NRF_SO_PDN_STATE, &state, sizeof(nrf_pdn_state_t));

The example code shows how to check whether a PDN socket is still active.

However, if a long time has elapsed after the last transmission on any socket bound to a PDN, the PDN socket state might be reported as active, even though the network has deactivated the PDN. In this case, sending data on any socket bound to that PDN will fail with error NRF_ENETDOWN and you must create another PDN socket. See Creating a PDN socket.

Note

Setting the socket option NRF_SO_PDN_STATE with nrf_setsockopt() to manually control activation/deactivation is not supported.

Retrieving the context ID (CID)

nrf_pdn_context_id_t cid;
nrf_getsockopt(pdn_fd, NRF_SOL_PDN, NRF_SO_PDN_CONTEXT_ID, &cid, sizeof(nrf_pdn_context_id_t));

In some use cases, it is useful to know the CID that is assigned to a PDN socket. To retrieve the CID, get the socket option NRF_SO_PDN_CONTEXT_ID with nrf_getsockopt().

NRF_SO_PDN_CONTEXT_ID is a read-only socket option. The CID is an internal allocation and cannot be configured by the user.

Sending a DNS query using APN

struct nrf_addrinfo hints = {
  .ai_family = NRF_AF_INET,
  .ai_socktype = NRF_SOCK_STREAM
}

struct nrf_addrinfo apn_hints;

apn_hints.ai_family = NRF_AF_LTE;
apn_hints.ai_socktype = NRF_SOCK_MGMT;
apn_hints.ai_protocol = NRF_PROTO_PDN;
apn_hints.ai_canonname = "custom.apn";

hints.ai_next = &apn_hints;

nrf_getaddrinfo("example.com", NULL, &hints, &result);

The example code shows how to specify an APN to use for a DNS query. In this example, the DNS hints are extended with an extra linked-list hint that specifies the APN to use for the query.

Like for data sockets, the APN name must be set up and activated before the DNS query. Otherwise, the query will fail.

Note

From Modem library v1.1.0, routing a DNS query by specifying the PDN ID is introduced and the use of an APN for DNS query is deprecated. It is recommended that the application specify the PDN ID for a DNS query.

Sending a DNS query using PDN ID

The following code shows how to specify a PDN ID to route a DNS query:

struct nrf_addrinfo hints = {
  .ai_flags = NRF_AI_PDNSERV, /* flag to specify PDN ID */
  .ai_family = NRF_AF_INET,
  .ai_socktype = NRF_SOCK_STREAM
}

nrf_getaddrinfo("example.com", "pdn1", &hints, &result);

Waiting for an IPv6 connection

at_fd = nrf_socket(NRF_AF_LTE, 0, NRF_NPROTO_AT);
nrf_send(at_fd, "AT+CGEREP=1", strlen("AT+CGEREP=1"));
# -> AT reponse "OK"

# Set up PDN socket using the APN (PDN initialize and connect)

nrf_getsockopt(pdn_fd, NRF_SOL_PDN, NRF_SO_PDN_CONTEXT_ID, &context_id, sizeof(uint8_t));
while not (context_id in ":CGEV IPV6 <cid>");
nrf_close(at_fd);

When requesting an IPv6 connection (which requires PDN type IPV6 or IPV4V6), the IPv6 address might not be correct even if the request is successful. The reason for this is that many networks use SLAAC, where the prefix of the IPv6 address is set after the PDN connection is created. Therefore, you might need to wait with using an IPv6 socket until the prefix is properly set. If the address cannot be obtained, a +CGEV: IPV6 FAIL <cid> error is returned.

The example code shows how to set up an AT socket that logs “CGEV” entries. It signals when IPv6 is available for use by an IPv6 socket, either while connecting to a PDN or afterwards.

Note that the example code does not cover the default PDN (CID 0), because this PDN is always available. However, the algorithm for checking that IPv6 is available on the default PDN is the same as if it was manually set up with an APN.