diff options
Diffstat (limited to 'inc/dnbd3/shared')
-rw-r--r-- | inc/dnbd3/shared/crc32.h | 9 | ||||
-rw-r--r-- | inc/dnbd3/shared/fdsignal.h | 57 | ||||
-rw-r--r-- | inc/dnbd3/shared/log.h | 65 | ||||
-rw-r--r-- | inc/dnbd3/shared/protocol.h | 156 | ||||
-rw-r--r-- | inc/dnbd3/shared/serialize.h | 40 | ||||
-rw-r--r-- | inc/dnbd3/shared/sockhelper.h | 120 | ||||
-rw-r--r-- | inc/dnbd3/shared/timing.h | 162 |
7 files changed, 609 insertions, 0 deletions
diff --git a/inc/dnbd3/shared/crc32.h b/inc/dnbd3/shared/crc32.h new file mode 100644 index 0000000..00b8bdd --- /dev/null +++ b/inc/dnbd3/shared/crc32.h @@ -0,0 +1,9 @@ +#ifndef _CRC32_H_ +#define _CRC32_H_ + +#include <stdint.h> + +uint32_t crc32(uint32_t crc, const uint8_t *buf, size_t len); + +#endif + diff --git a/inc/dnbd3/shared/fdsignal.h b/inc/dnbd3/shared/fdsignal.h new file mode 100644 index 0000000..960a2a9 --- /dev/null +++ b/inc/dnbd3/shared/fdsignal.h @@ -0,0 +1,57 @@ +#ifndef _FD_SIGNAL_H_ +#define _FD_SIGNAL_H_ + +#define SIGNAL_OK (0) +#define SIGNAL_TIMEOUT (-2) +#define SIGNAL_ERROR (-1) + +typedef struct _dnbd3_signal dnbd3_signal_t; + +/** + * Create a new signal, nonblocking. + * @return NULL on error, pointer to dnbd3_signal_t on success. + */ +dnbd3_signal_t* signal_new(); + +/** + * Create a new signal, blocking. + * @return NULL on error, pointer to dnbd3_signal_t on success. + */ +dnbd3_signal_t* signal_newBlocking(); + +/** + * Trigger the given signal, so a wait or clear call will succeed. + * @return SIGNAL_OK on success, SIGNAL_ERROR on error + */ +int signal_call(const dnbd3_signal_t* const signal); + +/** + * Wait for given signal, with an optional timeout. + * If timeout == 0, just poll once. + * If timeout < 0, wait forever. + * @return > 0 telling how many times the signal was called, + * SIGNAL_TIMEOUT if the timeout was reached, + * SIGNAL_ERROR if some error occured + */ +int signal_wait(const dnbd3_signal_t* const signal, int timeoutMs); + +/** + * Clears any pending signals on this signal. + * @return number of signals that were pending, + * SIGNAL_ERROR if some error occured + */ +int signal_clear(const dnbd3_signal_t* const signal); + +/** + * Close the given signal. + */ +void signal_close(const dnbd3_signal_t* const signal); + +/** + * Get a file descriptor for the given signal that can be + * waited on using poll or similar. + * @return -1 if the signal is invalid + */ +int signal_getWaitFd(const dnbd3_signal_t* const signal); + +#endif diff --git a/inc/dnbd3/shared/log.h b/inc/dnbd3/shared/log.h new file mode 100644 index 0000000..5b1e8f7 --- /dev/null +++ b/inc/dnbd3/shared/log.h @@ -0,0 +1,65 @@ +/* + * This file is part of the Distributed Network Block Device 3 + * + * Copyright(c) 2011-2012 Simon Rettberg + * + * This file may be licensed under the terms of of the + * GNU General Public License Version 2 (the ``GPL''). + * + * Software distributed under the License is distributed + * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the GPL for the specific language + * governing rights and limitations. + * + * You should have received a copy of the GPL along with this + * program. If not, go to http://www.gnu.org/licenses/gpl.html + * or write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef LOG_H_ +#define LOG_H_ + +#include <stdbool.h> +#include <unistd.h> + +typedef unsigned int logmask_t; +#define LOG_ERROR ((logmask_t)1) // Fatal error, server will terminate +#define LOG_WARNING ((logmask_t)2) // Major issue, something is broken but keep running +#define LOG_MINOR ((logmask_t)4) // Minor issue, more of a hickup than serious problem +#define LOG_INFO ((logmask_t)8) // Informational message +#define LOG_DEBUG1 ((logmask_t)16) // Debug information, use this for non-spammy stuff +#define LOG_DEBUG2 ((logmask_t)32) // Use this for debug messages that will show up a lot + + +/** + * Check if cansoleMask | fileMask has all of mask set. + */ +bool log_hasMask(const logmask_t mask); + +void log_setFileMask(logmask_t mask); + +void log_setConsoleMask(logmask_t mask); + +void log_setConsoleTimestamps(bool on); + +/** + * Open or reopen the log file. If path is NULL and the + * function was called with a path before, the same path + * will be used again. + */ +bool log_openLogFile(const char *path); + +/** + * Add a line to the log + */ +void logadd(const logmask_t mask, const char *text, ...) + __attribute__ ((format (printf, 2, 3))); + +/** + * Return last size bytes of log. + */ +ssize_t log_fetch(char *buffer, int size); + +#endif /* LOG_H_ */ diff --git a/inc/dnbd3/shared/protocol.h b/inc/dnbd3/shared/protocol.h new file mode 100644 index 0000000..1dd47f8 --- /dev/null +++ b/inc/dnbd3/shared/protocol.h @@ -0,0 +1,156 @@ +#ifndef _PROTOCOL_H_ +#define _PROTOCOL_H_ + +#include <dnbd3/types.h> +#include <dnbd3/shared/serialize.h> +#include <dnbd3/shared/sockhelper.h> + +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> + +// Client tells server that it is another server +#define FLAGS8_SERVER (1) +// Client (which is a proxy) tells server that it has background-replication enabled +#define FLAGS8_BG_REP (2) + +// 2017-10-16: We now support hop-counting, macro to pass hop count conditinally to a function +#define COND_HOPCOUNT(vers,hopcount) ( (vers) >= 3 ? (hopcount) : 0 ) + +// 2017-11-02: Macro to set flags in select image message properly if we're a server, as BG_REP depends on global var +#define SI_SERVER_FLAGS ( (uint8_t)( (_pretendClient ? 0 : FLAGS8_SERVER) | (_backgroundReplication == BGR_FULL ? FLAGS8_BG_REP : 0) ) ) + +#define REPLY_OK (0) +#define REPLY_ERRNO (-1) +#define REPLY_AGAIN (-2) +#define REPLY_INTR (-3) +#define REPLY_CLOSED (-4) +#define REPLY_INCOMPLETE (-5) +#define REPLY_WRONGMAGIC (-6) + +static inline int dnbd3_read_reply(int sock, dnbd3_reply_t *reply, bool wait) +{ + ssize_t ret = recv( sock, reply, sizeof(*reply), (wait ? MSG_WAITALL : MSG_DONTWAIT) | MSG_NOSIGNAL ); + if ( ret == 0 ) return REPLY_CLOSED; + if ( ret < 0 ) { + if ( errno == EAGAIN || errno == EWOULDBLOCK ) return REPLY_AGAIN; + if ( errno == EINTR ) return REPLY_INTR; + return REPLY_ERRNO; + } + if ( !wait && ret != sizeof(*reply) ) ret += recv( sock, ((char*)reply) + ret, sizeof(*reply) - ret, MSG_WAITALL | MSG_NOSIGNAL ); + if ( ret != sizeof(*reply) ) return REPLY_INCOMPLETE; + fixup_reply( *reply ); + if ( reply->magic != dnbd3_packet_magic ) return REPLY_WRONGMAGIC; + return REPLY_OK; +} + +static inline bool dnbd3_get_reply(int sock, dnbd3_reply_t *reply) +{ + int ret; + do { + ret = dnbd3_read_reply( sock, reply, true ); + } while ( ret == REPLY_INTR ); + return ret == REPLY_OK; +} + +static inline bool dnbd3_select_image(int sock, const char *name, uint16_t rid, uint8_t flags8) +{ + serialized_buffer_t serialized; + dnbd3_request_t request; + struct iovec iov[2]; + serializer_reset_write( &serialized ); + serializer_put_uint16( &serialized, PROTOCOL_VERSION ); + serializer_put_string( &serialized, name ); + serializer_put_uint16( &serialized, rid ); + serializer_put_uint8( &serialized, flags8 ); + const ssize_t len = serializer_get_written_length( &serialized ); + request.magic = dnbd3_packet_magic; + request.cmd = CMD_SELECT_IMAGE; + request.size = (uint32_t)len; + request.handle = 0; + request.offset = 0; + fixup_request( request ); + iov[0].iov_base = &request; + iov[0].iov_len = sizeof(request); + iov[1].iov_base = &serialized; + iov[1].iov_len = len; + ssize_t ret; + do { + ret = writev( sock, iov, 2 ); + } while ( ret == -1 && errno == EINTR ); + return ret == len + (ssize_t)sizeof(request); +} + +static inline bool dnbd3_get_block(int sock, uint64_t offset, uint32_t size, uint64_t handle, uint8_t hopCount) +{ + dnbd3_request_t request; + request.magic = dnbd3_packet_magic; + request.handle = handle; + request.cmd = CMD_GET_BLOCK; + // When writing before "fixup", we can get away with assigning to offset instead of offset_small if we + // do it before assigning to .hops. Faster on 64bit machines (so, on everything) + request.offset = offset; + request.hops = hopCount; + request.size = size; + fixup_request( request ); + return sock_sendAll( sock, &request, sizeof(request), 2 ) == (ssize_t)sizeof(request); +} + +static inline bool dnbd3_get_crc32(int sock, uint32_t *master, void *buffer, size_t *bufferLen) +{ + dnbd3_request_t request; + dnbd3_reply_t reply; + request.magic = dnbd3_packet_magic; + request.handle = 0; + request.cmd = CMD_GET_CRC32; + request.offset = 0; + request.size = 0; + fixup_request( request ); + if ( sock_sendAll( sock, &request, sizeof(request), 2 ) != (ssize_t)sizeof(request) ) return false; + if ( !dnbd3_get_reply( sock, &reply ) ) return false; + if ( reply.size == 0 ) { + *bufferLen = 0; + return true; + } + if ( reply.size < 4 ) return false; + reply.size -= 4; + if ( reply.cmd != CMD_GET_CRC32 || reply.size > *bufferLen ) return false; + *bufferLen = reply.size; + if ( sock_recv( sock, master, sizeof(uint32_t) ) != (ssize_t)sizeof(uint32_t) ) return false; + return sock_recv( sock, buffer, reply.size ) == (ssize_t)reply.size; +} + +/** + * Pass a full serialized_buffer_t and a socket fd. Parsed data will be returned in further arguments. + * Note that all strings will point into the passed buffer, so there's no need to free them. + * This function will also read the header for you, as this message can only occur during connection, + * where no unrequested messages could arrive inbetween. + */ +static inline bool dnbd3_select_image_reply(serialized_buffer_t *buffer, int sock, uint16_t *protocol_version, char **name, uint16_t *rid, + uint64_t *imageSize) +{ + errno = 0; + dnbd3_reply_t reply; + if ( !dnbd3_get_reply( sock, &reply ) ) { + return false; + } + errno = 0; + if ( reply.cmd != CMD_SELECT_IMAGE || reply.size < 3 || reply.size > MAX_PAYLOAD ) { + return false; + } + // receive reply payload + ssize_t ret = sock_recv( sock, buffer, reply.size ); + if ( ret != (ssize_t)reply.size ) { + return false; + } + // handle/check reply payload + serializer_reset_read( buffer, reply.size ); + *protocol_version = serializer_get_uint16( buffer ); + *name = serializer_get_string( buffer ); + *rid = serializer_get_uint16( buffer ); + *imageSize = serializer_get_uint64( buffer ); + return true; +} + +#endif diff --git a/inc/dnbd3/shared/serialize.h b/inc/dnbd3/shared/serialize.h new file mode 100644 index 0000000..a7a1ced --- /dev/null +++ b/inc/dnbd3/shared/serialize.h @@ -0,0 +1,40 @@ +#ifndef SERIALIZER_H_ +#define SERIALIZER_H_ + +#include <dnbd3/config.h> +#include <dnbd3/types.h> + +typedef struct +{ + char buffer[MAX_PAYLOAD]; // This MUST be the first member or send_reply() will blow up + char *buffer_end; + char *buffer_pointer; +} serialized_buffer_t; + +void serializer_reset_read(serialized_buffer_t *buffer, size_t data_len); + +void serializer_reset_write(serialized_buffer_t *buffer); + +uint32_t serializer_get_written_length(serialized_buffer_t *buffer); + +// + +uint8_t serializer_get_uint8(serialized_buffer_t *buffer); + +uint16_t serializer_get_uint16(serialized_buffer_t *buffer); + +uint64_t serializer_get_uint64(serialized_buffer_t *buffer); + +char *serializer_get_string(serialized_buffer_t *buffer); + +// + +void serializer_put_uint8(serialized_buffer_t *buffer, uint8_t value); + +void serializer_put_uint16(serialized_buffer_t *buffer, uint16_t value); + +void serializer_put_uint64(serialized_buffer_t *buffer, uint64_t value); + +void serializer_put_string(serialized_buffer_t *buffer, const char *value); + +#endif diff --git a/inc/dnbd3/shared/sockhelper.h b/inc/dnbd3/shared/sockhelper.h new file mode 100644 index 0000000..5c7d903 --- /dev/null +++ b/inc/dnbd3/shared/sockhelper.h @@ -0,0 +1,120 @@ +#ifndef SOCKHELPER_H_ +#define SOCKHELPER_H_ + +/* + * Helper functions for dealing with sockets. These functions should + * abstract from the IP version by using getaddrinfo() and thelike. + */ + +#include <dnbd3/types.h> +#include <stdint.h> +#include <sys/socket.h> +#include <string.h> + +typedef struct _poll_list poll_list_t; + +/** + * Connect to given dnbd3_host_t. + * @param addr - address of host to connect to + * @param connect_ms - timeout in milliseconds after which the connection attempt fails + * @param rw_ms - read/write timeout in milliseconds to apply on successful connect + * @return socket file descriptor, or -1 on error + */ +int sock_connect(const dnbd3_host_t * const addr, const int connect_ms, const int rw_ms); + +/** + * Resolve/parse given address and put the result(s) into passed dnbd3_host_t array, + * but only up to count entries. + * @return Number of items added to array + */ +int sock_resolveToDnbd3Host(const char * const address, dnbd3_host_t * const dest, const int count); + +bool sock_sockaddrToDnbd3(struct sockaddr* sa, dnbd3_host_t *host); + +void sock_setTimeout(const int sockfd, const int milliseconds); + +size_t sock_printHost(const dnbd3_host_t * const host, char *output, const size_t len); + +size_t sock_printable(const struct sockaddr * const addr, const socklen_t addrLen, char *output, const size_t len); + +/** + * Create new poll list. + */ +poll_list_t* sock_newPollList(); + +/** + * Delete a poll list, closing all sockets first if necessary. + */ +void sock_destroyPollList(poll_list_t *list); + +/** + * Listen on all interfaces/available IP addresses, using the given protocol. + * IPv4 and IPv6 are supported. + * @param protocol_family PF_INET or PF_INET6 + * @param port port to listen on + * @return true if any listen call was successful + */ +bool sock_listenAny(poll_list_t* list, uint16_t port); + +/** + * Listen on a specific address and port. + * @param bind_addr human readable address to bind to for listening + * @param port to listen on + */ +bool sock_listen(poll_list_t* list, char* bind_addr, uint16_t port); + +/** + * Asynchroneously connect to multiple hosts. + * This can be called multiple times with varying timeouts. Calling it + * the first time on an empty list is identical to sock_connect(). On + * consecutive calls, more nonblocking sockets in connecting state will + * be added to the list, and on each of these calls, all the pending + * sockets will be checked for successful connection (or error), respecting + * the passed timeout. + * host can be NULL to just wait on the sockets already in the list. + * If at least one socket completed the connection + * within the given timeout, it will be removed from the list and + * returned. On error or timeout, -1 is returned. If there are no more sockets + * in the list, -2 is returned. + */ +int sock_multiConnect(poll_list_t* list, const dnbd3_host_t* host, int connect_ms, int rw_ms); + +/** + * This is a multi-socket version of accept. Pass in an array of listening sockets. + * If any of the sockets has an incoming connection, accept it and return the new socket's fd. + * On error, return -1, just like accept(). + * @param sockets array of listening socket fds + * @param socket_count number of sockets in that array + * @return fd of new client socket, -1 on error + */ +int sock_accept(poll_list_t *list, struct sockaddr_storage *addr, socklen_t *length_ptr); + +void sock_set_nonblock(int sock); + +void sock_set_block(int sock); + +/** + * Add given socket to array. Take an existing empty slot ( == -1) if available, + * append to end otherwise. Updates socket count variable passed by reference. + * + * @param poll_list_t list the poll list to add the socket to + * @param sock socket fd to add + * @param wantRead whether to set the EPOLLIN flag + * @param wantWrite whether to set the EPOLLOUT flag + * @return true on success, false iff the array is already full or socket is < 0 + */ +bool sock_append(poll_list_t *list, const int sock, bool wantRead, bool wantWrite); + +/** + * Send the whole buffer, calling write() multiple times if neccessary. + * Give up after calling write() maxtries times. + * Set maxtries < 0 to try infinitely. + */ +ssize_t sock_sendAll(const int sock, const void *buffer, const size_t len, int maxtries); + +/** + * Send given buffer, repeatedly calling recv on partial send or EINTR. + */ +ssize_t sock_recv(const int sock, void *buffer, const size_t len); + +#endif /* SOCKHELPER_H_ */ diff --git a/inc/dnbd3/shared/timing.h b/inc/dnbd3/shared/timing.h new file mode 100644 index 0000000..f23bfeb --- /dev/null +++ b/inc/dnbd3/shared/timing.h @@ -0,0 +1,162 @@ +#ifndef _D_TIMING_H +#define _D_TIMING_H + +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 199309L +#endif + +#include <time.h> +#include <stdint.h> +#include <stdbool.h> + +#ifdef CLOCK_MONOTONIC_RAW +#define BEST_CLOCK_SOURCE CLOCK_MONOTONIC_RAW +#else +#define BEST_CLOCK_SOURCE CLOCK_MONOTONIC +#endif + +typedef struct timespec ticks; + +extern struct timespec basetime; + +/** + * Assign src to dst while adding secs seconds. + */ +#define timing_set(dst,src,secs) do { (dst)->tv_sec = (src)->tv_sec + (secs); (dst)->tv_nsec = (src)->tv_nsec; } while (0) + +/** + * Define variable now, initialize to timing_get. + */ +#define declare_now ticks now; timing_get( &now ) + +/** + * Call this once to calibrate on startup. + * Although overflows of CLOCK_MONOTONIC(_RAW) should + * by definition never happen, we still have a fixed size + * int that could at some point. By forcing the counter + * to start at 0 on startup the point of overflow + * will be very far in the future (decades for 32bit time_t, + * end of universe for 64bit). + */ +void timing_setBase(); + +/** + * Internal, do not use. Moved to another function + * to prevent inlining of error handling code, which + * should be very unlikely to ever trigger. + */ +_Noreturn void timing_abort(); + +/** + * Get current time. Shortcut for clock_gettime with error check. + */ +static inline void timing_get(ticks* retval) +{ + if ( clock_gettime( BEST_CLOCK_SOURCE, retval ) == -1 ) timing_abort(); + retval->tv_sec -= basetime.tv_sec; +} + +/** + * Get a ticks instance somewhere in the future. + * Useful for timeouts. + */ +static inline void timing_gets(ticks* retval, int32_t addSeconds) +{ + timing_get( retval ); + retval->tv_sec += addSeconds; +} + +static inline void timing_addSeconds(ticks* retval, ticks* base, int32_t addSeconds) +{ + retval->tv_sec = base->tv_sec + addSeconds; + retval->tv_nsec = base->tv_nsec; +} + +/** + * Check whether given timeout is reached. + * Might trigger up to one second early. + */ +static inline bool timing_reached(const ticks* timeout, const ticks* now) +{ + return now->tv_sec >= timeout->tv_sec; +} +#define timing_1le2(one,two) timing_reached(one,two) + +/** + * Precise check whether given timeout has been reached. + */ +static inline bool timing_reachedPrecise(const ticks* timeout, const ticks* now) +{ + return now->tv_sec > timeout->tv_sec + || (now->tv_sec == timeout->tv_sec && now->tv_nsec > timeout->tv_nsec); +} + +/** + * Shortcut for above. Useful if not used in loop. + * Might trigger up to one second early. + */ +static inline bool timing_isReached(const ticks* timeout) +{ + ticks now; + timing_get( &now ); + return timing_reached( timeout, &now ); +} +/** + * Shortcut for above. Useful if not used in loop. + */ +static inline bool timing_isReachedPrecise(const ticks* timeout) +{ + ticks now; + timing_get( &now ); + return timing_reachedPrecise( timeout, &now ); +} + + +/** + * Get difference between two ticks, rounded down to seconds. + * Make sure you pass the arguments in the proper order. If + * end is before start, 0 will always be returned. + */ +static inline uint32_t timing_diff(const ticks *start, const ticks *end) +{ + if ( end->tv_sec <= start->tv_sec ) return 0; + return (uint32_t)( ( end->tv_sec - start->tv_sec ) + + ( start->tv_nsec > end->tv_nsec ? -1 : 0 ) ); +} + +/** + * Get difference between two ticks, rounded down to milliseconds. + * Same as above; passing arguments in reverse will always return 0. + */ +static inline uint64_t timing_diffMs(const ticks *start, const ticks *end) +{ + if ( end->tv_sec < start->tv_sec ) return 0; + uint64_t diff = (uint64_t)( end->tv_sec - start->tv_sec ) * 1000; + if ( start->tv_nsec >= end->tv_nsec ) { + if ( diff == 0 ) return 0; + diff -= (start->tv_nsec - end->tv_nsec) / 1000000; + } else { + diff += (end->tv_nsec - start->tv_nsec) / 1000000; + } + return diff; +} + +/** + * Get difference between two ticks, rounded down to microseconds. + * Same as above; passing arguments in reverse will always return 0. + */ +static inline uint64_t timing_diffUs(const ticks *start, const ticks *end) +{ + if ( end->tv_sec < start->tv_sec ) return 0; + uint64_t diff = (uint64_t)( end->tv_sec - start->tv_sec ) * 1000000; + if ( start->tv_nsec >= end->tv_nsec ) { + if ( diff == 0 ) return 0; + diff -= ( start->tv_nsec - end->tv_nsec ) / 1000; + } else { + diff += ( end->tv_nsec - start->tv_nsec ) / 1000; + } + return diff; +} + + +#endif |