8.3.1.4. Remote hostname and shutdown operations

Note

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

8.3.1.4.1. Introduction

This tutorial builds up on the previous one and adds support for the “get remote hostname” and “shutdown” operations.

These operations will allow suspending and resuming CoAP downloads when using the “offline mode” functionality.

8.3.1.4.1.1. Get remote hostname operation

typedef avs_error_t (*avs_net_socket_get_remote_hostname_t)(
        avs_net_socket_t *socket, char *out_buffer, size_t out_buffer_size);

This operation is similar in concept to the previously introduced Get remote host operation. However, “get remote host” is intended to always return a stringified IP address, while “get remote hostname” shall return the hostname originally passed to the Connect function.

8.3.1.4.1.2. Shutdown operation

typedef avs_error_t (*avs_net_socket_shutdown_t)(avs_net_socket_t *socket);

This API is intended as a parallel to the POSIX shutdown() function (called with SHUT_RDWR mode) - it shall disconnect the socket on the transport control layer, but does not close the OS-level socket descriptor.

This shall put the socket in a state similar to closed, but with the connection association still in place. This is mostly done to ensure that all “get remote/local host/port” operations keep returning the same data while the connection is unavailable from the network.

This operation has additional semantics for (D)TLS sockets - it will shut down the underlying raw socket without gracefully closing the connection on the (D)TLS layer. This is however implemented within the (D)TLS backend integration and is outside the scope of this implementation.

8.3.1.4.2. Additional socket state

The string passed to the Connect function is not stored anywhere in our current logic; there is also no standard POSIX API to determine whether the socket is in the shut down state. That’s why we will need additional fields in our socket structure to implement these operations:

typedef struct {
    const avs_net_socket_v_table_t *operations;
    int socktype;
    int fd;
    avs_time_duration_t recv_timeout;
    char remote_hostname[256];
    bool shut_down;
} net_socket_impl_t;

The remote_hostname field will contain the last known hostname to which the connection was successful.

In our implementation, the shut_down flag is intended to only be true if the socket is specifically in the “shut down” state - if the socket is either bound, connected or closed, it shall be false.

8.3.1.4.3. Updating the socket state

The only place where there is direct access to the hostname, is the Connect function, so we need to update it accordingly to cache this information if the connection is successful:

static avs_error_t
net_connect(avs_net_socket_t *sock_, const char *host, const char *port) {
    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;
    struct addrinfo hints = {
        .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(host, 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) {
        err = avs_errno(AVS_UNKNOWN_ERROR);
    } else if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {
        err = avs_errno(AVS_ECONNREFUSED);
    }
    if (avs_is_ok(err)) {
        sock->shut_down = false;
        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), "%s",
                 host);
    }
    freeaddrinfo(addr);
    return err;
}

Note that in addition to saving the hostname, we also set the shut_down flag to false. This is because we entered the “connected” state, and - as described above, the flag is only intended to be true when the socket is in the “shut down” state.

For this reason, we also need to update this flag in the bind and close operations:

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);
    } else {
        sock->shut_down = false;
    }
    if (avs_is_err(err) && sock->fd >= 0) {
        close(sock->fd);
        sock->fd = -1;
    }
    freeaddrinfo(addr);
    return err;
}

static avs_error_t net_close(avs_net_socket_t *sock_) {
    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;
    avs_error_t err = AVS_OK;
    if (sock->fd >= 0) {
        if (close(sock->fd)) {
            err = avs_errno(AVS_EIO);
        }
        sock->fd = -1;
        sock->shut_down = false;
    }
    return err;
}

8.3.1.4.4. Update to get_opt implementation

We need to fix implementation of getting the AVS_NET_SOCKET_OPT_STATE option so that the “shut down” state is properly reported:

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 if (sock->shut_down) {
            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;
        } 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);
    }
}

8.3.1.4.5. New method implementations

Implementation of the shutdown operation method is simple and self-explanatory:

static avs_error_t net_shutdown(avs_net_socket_t *sock_) {
    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;
    avs_error_t err = avs_errno(AVS_EBADF);
    if (sock->fd >= 0) {
        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;
        sock->shut_down = true;
    }
    return err;
}

Similarly, the “get remote hostname” method code is just a simple string copy:

static avs_error_t net_remote_hostname(avs_net_socket_t *sock_,
                                       char *out_buffer,
                                       size_t out_buffer_size) {
    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;
    return avs_simple_snprintf(out_buffer, out_buffer_size, "%s",
                               sock->remote_hostname)
                           < 0
                   ? avs_errno(AVS_UNKNOWN_ERROR)
                   : AVS_OK;
}

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,
    .shutdown = net_shutdown,
    .cleanup = net_cleanup,
    .get_system_socket = net_system_socket,
    .get_remote_host = net_remote_host,
    .get_remote_hostname = net_remote_hostname,
    .get_remote_port = net_remote_port,
    .get_local_port = net_local_port,
    .get_opt = net_get_opt,
    .set_opt = net_set_opt
};

Note

Due to lack of support for IP address stickiness, when resuming CoAP downloads using this code, it might happen that the resumed download will connect to a different node than the original one.

This limitation will be addressed in a subsequent tutorial.