8.3.1.3. Bind operation

Note

Code related to this tutorial can be found under examples/custom-network/bind in the Anjay source directory.

8.3.1.3.1. Introduction

This tutorial builds up on the previous one and adds support for the bind operation.

This will allow use of the anjay_configuration_t::udp_listen_port setting, which might be useful e.g. for the LwM2M 1.0-style Server-Initiated Bootstrap.

We will also add support for the “get local port” operation, which will allow even ephemeral listening port number to be retained between subsequent connections to the same server.

8.3.1.3.2. Bind operation itself

Implementation of the bind function is very similar to the previously implemented Connect one. Important changes are highlighted.

static avs_error_t
net_bind(avs_net_socket_t *sock_, const char *address, const char *port) {
    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;
    struct addrinfo hints = {
        .ai_flags = AI_PASSIVE,
        .ai_socktype = sock->socktype
    };
    if (sock->fd >= 0) {
        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,
                   &(socklen_t) { sizeof(hints.ai_family) });
    }
    struct addrinfo *addr = NULL;
    avs_error_t err = AVS_OK;
    if (getaddrinfo(address, port, &hints, &addr) || !addr) {
        err = avs_errno(AVS_EADDRNOTAVAIL);
    } else if ((sock->fd < 0
                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,
                                      addr->ai_protocol))
                           < 0)
               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },
                             sizeof(int))) {
        err = avs_errno(AVS_UNKNOWN_ERROR);
    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {
        err = avs_errno(AVS_ECONNREFUSED);
    }
    if (avs_is_err(err) && sock->fd >= 0) {
        close(sock->fd);
        sock->fd = -1;
    }
    freeaddrinfo(addr);
    return err;
}

This time getaddrinfo() is called with AI_PASSIVE flag to allow wildcard addresses (e.g. 0.0.0.0) and to prevent DNS resolution for such local addresses.

Of course, bind() is called instead of connect(). But before doing so, setsockopt() is called to enable the SO_REUSEADDR flag. This is done because Anjay may create multiple sockets bound to the same port, one for each remote server connection. This shall not result in a conflict, as all those sockets will be connected to different remote servers shortly after binding.

Note

More properly, SO_REUSEADDR should only be used if the reuse_addr flag has been set in the avs_net_socket_configuration_t structure passed at socket creation time.

However, Anjay always sets this flag to true, so it is alright to set it unconditionally in such simplistic implementation.

Finally, in case of error, the underlying socket descriptor is closed. This is to ensure that upon error, the socket will not end up in the “bound” state, which will be evident in the modifications to net_get_opt() illustrated below.

8.3.1.3.3. Changes to net_get_opt()

Changes to this function are highlighted:

static avs_error_t net_get_opt(avs_net_socket_t *sock_,
                               avs_net_socket_opt_key_t option_key,
                               avs_net_socket_opt_value_t *out_option_value) {
    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;
    switch (option_key) {
    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:
        out_option_value->recv_timeout = sock->recv_timeout;
        return AVS_OK;
    case AVS_NET_SOCKET_OPT_STATE:
        if (sock->fd < 0) {
            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;
        } else {
            sockaddr_union_t addr;
            if (!getpeername(sock->fd, &addr.addr,
                             &(socklen_t) { sizeof(addr) })
                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)
                        || (addr.in6.sin6_family == AF_INET6
                            && addr.in6.sin6_port != 0))) {
                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;
            } else {
                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;
            }
        }
        return AVS_OK;
    case AVS_NET_SOCKET_OPT_INNER_MTU:
        out_option_value->mtu = 1464;
        return AVS_OK;
    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:
        out_option_value->flag = false;
        return AVS_OK;
    default:
        return avs_errno(AVS_ENOTSUP);
    }
}

The original variant assumed that if the socket descriptor was present, it is connected. Here, we need to differentiate between the “connected” and “bound” states - hence we use the getpeername() function to check if there is a valid remote address.

Because getpeername() might return different kind of socket addresses, the sockaddr_union_t type declared in the previous tutorial is used.

8.3.1.3.4. Get local port operation

The “get local port” operation may or may not be implemented. It is not necessary for the bind operation to work, but if implemented, it will allow Anjay to keep ephemeral listening port number consistent across subsequent connections to the same server if anjay_configuration_t::udp_listen_port is not set.

Its implementation mirrors the Get remote port operation from the previous tutorial, only with getsockname() used instead of getpeername():

static avs_error_t net_local_port(avs_net_socket_t *sock_,
                                  char *out_buffer,
                                  size_t out_buffer_size) {
    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;
    sockaddr_union_t addr;
    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {
        return avs_errno(AVS_UNKNOWN_ERROR);
    }
    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);
}

8.3.1.3.5. Update to vtable

Of course the newly implemented functions need to be referenced in the virtual method table:

static const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {
    .connect = net_connect,
    .send = net_send,
    .receive = net_receive,
    .bind = net_bind,
    .close = net_close,
    .cleanup = net_cleanup,
    .get_system_socket = net_system_socket,
    .get_remote_host = net_remote_host,
    .get_remote_port = net_remote_port,
    .get_local_port = net_local_port,
    .get_opt = net_get_opt,
    .set_opt = net_set_opt
};