#include "nbdtypes.h" #include "ioutil.h" #include "sockutil.h" #include "util.h" #include "serve.h" #include #include #include int socket_connect(struct sockaddr* to, struct sockaddr* from) { int fd = socket(to->sa_family == AF_INET ? PF_INET : PF_INET6, SOCK_STREAM, 0); if( fd < 0 ){ warn( "Couldn't create client socket"); return -1; } if (NULL != from) { if ( 0 > bind( fd, from, sizeof(struct sockaddr_in6 ) ) ){ warn( SHOW_ERRNO( "bind() to source address failed" ) ); if ( 0 > close( fd ) ) { /* Non-fatal leak */ warn( SHOW_ERRNO( "Failed to close fd %i", fd ) ); } return -1; } } if ( 0 > sock_try_connect( fd, to, sizeof( struct sockaddr_in6 ), 15 ) ) { warn( SHOW_ERRNO( "connect failed" ) ); if ( 0 > close( fd ) ) { /* Non-fatal leak */ warn( SHOW_ERRNO( "Failed to close fd %i", fd ) ); } return -1; } return fd; } int socket_nbd_read_hello(int fd, off64_t * out_size) { struct nbd_init init; if ( 0 > readloop(fd, &init, sizeof(init)) ) { warn( "Couldn't read init" ); goto fail; } if (strncmp(init.passwd, INIT_PASSWD, 8) != 0) { warn("wrong passwd"); goto fail; } if (be64toh(init.magic) != INIT_MAGIC) { warn("wrong magic (%x)", be64toh(init.magic)); goto fail; } if ( NULL != out_size ) { *out_size = be64toh(init.size); } return 1; fail: return 0; } int socket_nbd_write_hello(int fd, off64_t out_size) { struct nbd_init init; struct nbd_init_raw init_raw; memcpy(&init.passwd, INIT_PASSWD, 8); init.magic = INIT_MAGIC; init.size = out_size; memset( &init_raw, 0, sizeof( init_raw ) ); // ensure reserved is 0s nbd_h2r_init(&init, &init_raw); if ( 0 > writeloop( fd, &init_raw, sizeof( init_raw ) ) ) { warn( SHOW_ERRNO( "failed to write hello to socket" ) ); return 0; } return 1; } void fill_request(struct nbd_request *request, int type, off64_t from, int len) { request->magic = htobe32(REQUEST_MAGIC); request->type = htobe32(type); ((int*) request->handle)[0] = rand(); ((int*) request->handle)[1] = rand(); request->from = htobe64(from); request->len = htobe32(len); } void read_reply(int fd, struct nbd_request *request, struct nbd_reply *reply) { struct nbd_reply_raw reply_raw; ERROR_IF_NEGATIVE(readloop(fd, &reply_raw, sizeof(struct nbd_reply_raw)), "Couldn't read reply"); nbd_r2h_reply( &reply_raw, reply ); if (reply->magic != REPLY_MAGIC) { error("Reply magic incorrect (%x)", reply->magic); } if (reply->error != 0) { error("Server replied with error %d", reply->error); } if (strncmp(request->handle, reply->handle, 8) != 0) { error("Did not reply with correct handle"); } } void wait_for_data( int fd, int timeout_secs ) { fd_set fds; struct timeval tv = { timeout_secs, 0 }; int selected; FD_ZERO( &fds ); FD_SET( fd, &fds ); selected = sock_try_select( FD_SETSIZE, &fds, NULL, NULL, timeout_secs >=0 ? &tv : NULL ); FATAL_IF( -1 == selected, "Select failed" ); ERROR_IF( 0 == selected, "Timed out waiting for reply" ); } void socket_nbd_read(int fd, off64_t from, int len, int out_fd, void* out_buf, int timeout_secs) { struct nbd_request request; struct nbd_reply reply; fill_request(&request, REQUEST_READ, from, len); FATAL_IF_NEGATIVE(writeloop(fd, &request, sizeof(request)), "Couldn't write request"); wait_for_data( fd, timeout_secs ); read_reply(fd, &request, &reply); if (out_buf) { FATAL_IF_NEGATIVE(readloop(fd, out_buf, len), "Read failed"); } else { FATAL_IF_NEGATIVE( splice_via_pipe_loop(fd, out_fd, len), "Splice failed" ); } } void socket_nbd_write(int fd, off64_t from, int len, int in_fd, void* in_buf, int timeout_secs) { struct nbd_request request; struct nbd_reply reply; fill_request(&request, REQUEST_WRITE, from, len); ERROR_IF_NEGATIVE(writeloop(fd, &request, sizeof(request)), "Couldn't write request"); if (in_buf) { ERROR_IF_NEGATIVE(writeloop(fd, in_buf, len), "Write failed"); } else { ERROR_IF_NEGATIVE( splice_via_pipe_loop(in_fd, fd, len), "Splice failed" ); } wait_for_data( fd, timeout_secs ); read_reply(fd, &request, &reply); } int socket_nbd_disconnect( int fd ) { int success = 1; struct nbd_request request; fill_request( &request, REQUEST_DISCONNECT, 0, 0 ); /* FIXME: This shouldn't be a FATAL error. We should just drop * the mirror without affecting the main server. */ FATAL_IF_NEGATIVE( writeloop( fd, &request, sizeof( request ) ), "Failed to write the disconnect request." ); return success; } #define CHECK_RANGE(error_type) { \ off64_t size;\ int success = socket_nbd_read_hello(params->client, &size); \ if ( success ) {\ if (params->from < 0 || (params->from + params->len) > size) {\ fatal(error_type \ " request %d+%d is out of range given size %d", \ params->from, params->len, size\ );\ }\ }\ else {\ fatal( error_type " connection failed." );\ }\ } void do_read(struct mode_readwrite_params* params) { params->client = socket_connect(¶ms->connect_to.generic, ¶ms->connect_from.generic); FATAL_IF_NEGATIVE( params->client, "Couldn't connect." ); CHECK_RANGE("read"); socket_nbd_read(params->client, params->from, params->len, params->data_fd, NULL, 10); close(params->client); } void do_write(struct mode_readwrite_params* params) { params->client = socket_connect(¶ms->connect_to.generic, ¶ms->connect_from.generic); FATAL_IF_NEGATIVE( params->client, "Couldn't connect." ); CHECK_RANGE("write"); socket_nbd_write(params->client, params->from, params->len, params->data_fd, NULL, 10); close(params->client); }