Monday, March 05, 2018

Samba: Handling new connections

Samba uses the tevents library to handle new incoming connections. The Samba server makes use of event handling to perform tasks such as creating a new process for each new client connection and to further handle new requests made by this client. Before the Samba server can handle these events, the events meant to be caught have to be registered along with a handler which handles these events.

More information on the tevent library is available at
https://tevent.samba.org/tevent_tutorial.html

We look at the code patch at the start of the smbd process. We start in main().
 int main(int argc,const char *argv[])
{
..
        struct tevent_context *ev_ctx;
..
        /*
         * Initialize the event context. The event context needs to be
         * initialized before the messaging context, cause the messaging
         * context holds an event context.
         */
        // This eventually returns tevent_context_init(NULL)
        ev_ctx = server_event_context();
        if (ev_ctx == NULL) {
                exit(1);
        }
..
        //Read notes on this function below.
        if (!open_sockets_smbd(parent, ev_ctx, msg_ctx, ports))
                exit_server("open_sockets_smbd() failed");
..
        //Loop and wait for events.
        //This function is where the tevent contexts such as handling
        //incoming requests or reading new information on the socket.
        smbd_parent_loop(ev_ctx, parent);
..
}

The function which opens sockets and adds the necessary tevents required to handle socket communication
static bool open_sockets_smbd(struct smbd_parent_context *parent,
                              struct tevent_context *ev_ctx,
                              struct messaging_context *msg_ctx,
                              const char *smb_ports)
{
..
                                /*
                                 * If we fail to open any sockets
                                 * in this loop the parent-sockets == NULL
                                 * case below will prevent us from starting.
                                 */

                                (void)smbd_open_one_socket(parent,
                                                  ev_ctx,
                                                  &ss,
                                                  port);
..
}

static bool smbd_open_one_socket(struct smbd_parent_context *parent,
                                 struct tevent_context *ev_ctx,
                                 const struct sockaddr_storage *ifss,
                                 uint16_t port)
{
..
        s->fd = open_socket_in(SOCK_STREAM,
                               port,
                               parent->sockets == NULL ? 0 : 2,
                               ifss,
                               true);
..
        /* ready to listen */
        set_socket_options(s->fd, "SO_KEEPALIVE");
        set_socket_options(s->fd, lp_socket_options());

        /* Set server socket to
         * non-blocking for the accept. */
        set_blocking(s->fd, False);
..
        //This sets the tevent context for the parent sockets.
        //The handling function smbd_accept_connection()
        //is responsible for accepting new client connections.
        s->fde = tevent_add_fd(ev_ctx,
                               s,
                               s->fd, TEVENT_FD_READ,
                               smbd_accept_connection,
                               s);
..
        tevent_fd_set_close_fn(s->fde, smbd_open_socket_close_fn);
..
}

On the parent processes, the process loops waiting for events to be handled.
int main(int argc,const char *argv[])
{
..
        smbd_parent_loop(ev_ctx, parent);
..
}

A new incoming request triggers the tevent context for the fd which has the handling function set to
smbd_accept_connection().
static void smbd_accept_connection(struct tevent_context *ev,
                                   struct tevent_fd *fde,
                                   uint16_t flags,
                                   void *private_data)
{
..
        pid = fork();
        //For child process
        if (pid == 0) {
..
                //Process the incoming request.
                smbd_process(ev, msg_ctx, fd, false);
         exit:
                exit_server_cleanly("end of child");
                return;
        }
        //For the parent process ie. main smbd process.

        /* The parent doesn't need this socket */
        close(fd);
..
        if (pid != 0) {
                add_child_pid(s->parent, pid);
        }
..
}
The parent process at this point forks a child process which is used to handle the new client. The parent process continues looping in main().

Below is the code path followed by the clild process.


void smbd_process(struct tevent_context *ev_ctx,
                  struct messaging_context *msg_ctx,
                  int sock_fd,
                  bool interactive)
{
..
        struct smbXsrv_client *client = NULL;
        ..
        struct smbXsrv_connection *xconn = NULL;
..
        //Create a new struct to store information about this client
        status = smbXsrv_client_create(ev_ctx, ev_ctx, msg_ctx, now, &client);
..
        //The connection information itself is stored in xconn
        status = smbd_add_connection(client, sock_fd, &xconn);
..
        //Loop and wait for new events.
        ret = tevent_loop_wait(ev_ctx);
..
}

With multichannel support, we can have multiple connections connected to the same client. We do not consider multichannel in this document.

NTSTATUS smbd_add_connection(struct smbXsrv_client *client, int sock_fd,
                             struct smbXsrv_connection **_xconn)
{
..
        xconn = talloc_zero(client, struct smbXsrv_connection);
..
        xconn->transport.fde = tevent_add_fd(client->ev_ctx,
                                             xconn,
                                             sock_fd,
                                             TEVENT_FD_READ,
                                             smbd_server_connection_handler,
                                             xconn);
..
        /* for now we only have one connection */
        DLIST_ADD_END(client->connections, xconn);
        xconn->client = client;
..
}
The new connection represented by struct smbXsrv_connection xconn is now connected to the client.
The tevent handler for this socket is now changed to smbd_server_connection_handler().

The child smbd process is now attached to a single client connection. It loops in smbd_process()
void smbd_process(struct tevent_context *ev_ctx,
                  struct messaging_context *msg_ctx,
                  int sock_fd,
                  bool interactive)
{
..
        //Loop and wait for new events.
        ret = tevent_loop_wait(ev_ctx);
..
}

Any new data now sent to this socket will trigger the event handler smbd_server_connection_handler().


static void smbd_server_connection_handler(struct tevent_context *ev,
                                           struct tevent_fd *fde,
                                           uint16_t flags,
                                           void *private_data)
{
..
        if (!NT_STATUS_IS_OK(xconn->transport.status)) {
                /*
                 * we're not supposed to do any io
                 */
                TEVENT_FD_NOT_READABLE(xconn->transport.fde);
                TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
                return;
        }
..
        if (flags & TEVENT_FD_WRITE) {
                smbd_server_connection_write_handler(xconn);
                return;
        }
        if (flags & TEVENT_FD_READ) {
                smbd_server_connection_read_handler(xconn, xconn->transport.sock);
                return;
        }
}

static void smbd_server_connection_read_handler(
        struct smbXsrv_connection *xconn, int fd)
{
..
        status = receive_smb_talloc(mem_ctx, xconn, fd,
                                    (char **)(void *)&inbuf,
                                    0, /* timeout */
                                    &unread_bytes,
                                    &encrypted,
                                    &inbuf_len, &seqnum,
                                    !from_client /* trusted channel */);
..
        process_smb(xconn, inbuf, inbuf_len, unread_bytes,
                    seqnum, encrypted, NULL);
}
The call is then processed by the samba server as needed in process_smb.

Samba: Handling new connections

Samba uses the tevents library to handle new incoming connections. The Samba server makes use of event handling to perform tasks such as cre...