Monday, October 08, 2018

Samba: Triggering oplock breaks

I have been investigating the Samba code as part of my task to implement oplock break retry code for a multichannel setup. This is a small extract from my notes which looks into how the oplock break is triggered on the samba server.

Registering the oplock break handler

The smbd server forks a new smbd process to handle a new incoming request by a SMB client.

As part of initialisation of the new smbd process an oplock break handler is initialised.



void smbd_process(struct tevent_context *ev_ctx,
                  struct messaging_context *msg_ctx,
                  int sock_fd,
                  bool interactive)
{
..
        status = smbXsrv_client_create(ev_ctx, ev_ctx, msg_ctx, now, &client);
..
        status = smbd_add_connection(client, sock_fd, &xconn);
..
        /* Setup oplocks */
        if (!init_oplocks(sconn))
                exit_server("Failed to init oplocks");
..
}

The oplock message queue is registered along with the necessary call backs.


bool init_oplocks(struct smbd_server_connection *sconn)

{

        DEBUG(3,("init_oplocks: initializing messages.\n"));



        messaging_register(sconn->msg_ctx, sconn, MSG_SMB_BREAK_REQUEST,

                           process_oplock_break_message);

        messaging_register(sconn->msg_ctx, sconn, MSG_SMB_KERNEL_BREAK,

                           process_kernel_oplock_break);

        return true;

}


This is the oplock break handler which handles oplock break requests coming in for files opened by this smbd process.

Triggering an oplock break


For another smbd process attempting to open the file, we end up calling the handler open_file_ntcreate().

In this case, we are not looking at the scenario where kernel oplocks are enabled. Kernel oplocks are useful when you other processes(eg: NFS) using the filesystem exported by samba at the same time. Without kernel oplocks, other processes cannot safely use the filesystem since the locking info is stored by samba in its own databases.


tatic NTSTATUS open_file_ntcreate(connection_struct *conn,

                            struct smb_request *req,

                            uint32_t access_mask,               /* access bits (FILE_READ_DATA etc.) */

                            uint32_t share_access,      /* share constants (FILE_SHARE_READ etc) */

                            uint32_t create_disposition,        /* FILE_OPEN_IF etc. */

                            uint32_t create_options,    /* options such as delete on close. */

                            uint32_t new_dos_attributes,        /* attributes used for new file. */

                            int oplock_request,         /* internal Samba oplock codes. */

                            struct smb2_lease *lease,

                                                        /* Information (FILE_EXISTS etc.) */

                            uint32_t private_flags,     /* Samba specific flags. */

                            int *pinfo,

                            files_struct *fsp)

{

..

        /* ignore any oplock requests if oplocks are disabled */

        if (!lp_oplocks(SNUM(conn)) ||

            IS_VETO_OPLOCK_PATH(conn, smb_fname->base_name)) {

                /* Mask off everything except the private Samba bits. */

                oplock_request &= SAMBA_PRIVATE_OPLOCK_MASK;

        }

..

 //First open the file

        fsp_open = open_file(fsp, conn, req, parent_dir,

                             flags|flags2, unx_mode, access_mask,

                             open_access_mask, &new_file_created);

..

 //Fetch the share mode from the database or allocate a fresh one if record doesn't exist.

        lck = get_share_mode_lock(talloc_tos(), id,

                                  conn->connectpath,

                                  smb_fname, &old_write_time);

..

 //Check to see if oplocks are set and if they violate the share mode

        status = open_mode_check(conn, lck,

                                 access_mask, share_access);

..

 //If there is a sharing violation, delay for oplock.

        if (req != NULL) {

                /*

                 * Handle oplocks, deferring the request if delay_for_oplock()

                 * triggered a break message and we have to wait for the break

                 * response.

                 */

                bool delay;

                bool sharing_violation = NT_STATUS_EQUAL(

                        status, NT_STATUS_SHARING_VIOLATION);



  //Here we end up calling send_break_message() to the smbd pid which opened the file 1st.

                delay = delay_for_oplock(fsp, oplock_request, lease, lck,

                                         sharing_violation,

                                         create_disposition,

                                         first_open_attempt);

                if (delay) {

                        schedule_defer_open(lck, fsp->file_id,

                                            request_time, req);

                        TALLOC_FREE(lck);

                        fd_close(fsp);

                        return NT_STATUS_SHARING_VIOLATION;

                }

        }

..

 //And finally set the new oplock for the file.

        /*

         * Setup the oplock info in both the shared memory and

         * file structs.

         */

        status = grant_fsp_oplock_type(req, fsp, lck, oplock_request, lease);

        if (!NT_STATUS_IS_OK(status)) {

                TALLOC_FREE(lck);

                fd_close(fsp);

                return status;

        }



}


Thus the oplock break is trigerred.

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: Triggering oplock breaks

I have been investigating the Samba code as part of my task to implement oplock break retry code for a multichannel setup. This is a small e...