diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config.h | 3 | ||||
-rw-r--r-- | src/fuse/connection.c | 6 | ||||
-rw-r--r-- | src/server/altservers.c | 5 | ||||
-rw-r--r-- | src/server/globals.h | 9 | ||||
-rw-r--r-- | src/server/image.c | 6 | ||||
-rw-r--r-- | src/server/net.c | 25 | ||||
-rw-r--r-- | src/server/uplink.c | 47 | ||||
-rw-r--r-- | src/server/uplink.h | 4 | ||||
-rw-r--r-- | src/shared/protocol.h | 8 | ||||
-rw-r--r-- | src/types.h | 33 |
10 files changed, 101 insertions, 45 deletions
diff --git a/src/config.h b/src/config.h index 9f8ee10..50336af 100644 --- a/src/config.h +++ b/src/config.h @@ -32,7 +32,8 @@ // Protocol version should be increased whenever new features/messages are added, // so either the client or server can run in compatibility mode, or they can // cancel the connection right away if the protocol has changed too much -#define PROTOCOL_VERSION 2 +#define PROTOCOL_VERSION 3 +// 2017-10-16: Update to v3: Change header to support request hop-counting #define NUMBER_SERVERS 8 // Number of alt servers per image/device diff --git a/src/fuse/connection.c b/src/fuse/connection.c index 3f142c7..a3007fd 100644 --- a/src/fuse/connection.c +++ b/src/fuse/connection.c @@ -203,7 +203,7 @@ bool connection_read(dnbd3_async_t *request) pthread_mutex_lock( &connection.sendMutex ); enqueueRequest( request ); if ( connection.sockFd != -1 ) { - if ( !dnbd3_get_block( connection.sockFd, request->offset, request->length, (uint64_t)request ) ) { + if ( !dnbd3_get_block( connection.sockFd, request->offset, request->length, (uint64_t)request, 0 ) ) { shutdown( connection.sockFd, SHUT_RDWR ); connection.sockFd = -1; pthread_mutex_unlock( &connection.sendMutex ); @@ -501,7 +501,7 @@ static void probeAltServers() srv->consecutiveFails += 10; goto fail; } - if ( !dnbd3_get_block( sock, 0, RTT_BLOCK_SIZE, 0 ) ) { + if ( !dnbd3_get_block( sock, 0, RTT_BLOCK_SIZE, 0, 0 ) ) { logadd( LOG_DEBUG1, "-> block request fail" ); goto fail; } @@ -637,7 +637,7 @@ static void switchConnection(int sockFd, alt_server_t *srv) logadd( LOG_DEBUG1, "Requeue after server change" ); next = it->next; enqueueRequest( it ); - if ( connection.sockFd != -1 && !dnbd3_get_block( connection.sockFd, it->offset, it->length, (uint64_t)it ) ) { + if ( connection.sockFd != -1 && !dnbd3_get_block( connection.sockFd, it->offset, it->length, (uint64_t)it, 0 ) ) { logadd( LOG_WARNING, "Resending pending request failed, re-entering panic mode" ); shutdown( connection.sockFd, SHUT_RDWR ); connection.sockFd = -1; diff --git a/src/server/altservers.c b/src/server/altservers.c index bf9d8f2..1e4af7e 100644 --- a/src/server/altservers.c +++ b/src/server/altservers.c @@ -407,6 +407,7 @@ static void *altservers_main(void *data UNUSED) // Test them all int bestSock = -1; int bestIndex = -1; + int bestProtocolVersion = -1; unsigned int bestRtt = 0xfffffff; unsigned int currentRtt = 0xfffffff; for (itAlt = 0; itAlt < numAlts; ++itAlt) { @@ -439,7 +440,7 @@ static void *altservers_main(void *data UNUSED) imageSize, image->virtualFilesize, image->name ); } // Request first block (NOT random!) ++++++++++++++++++++++++++++++ - if ( !dnbd3_get_block( sock, 0, DNBD3_BLOCK_SIZE, 0 ) ) { + if ( !dnbd3_get_block( sock, 0, DNBD3_BLOCK_SIZE, 0, COND_HOPCOUNT( protocolVersion, 1 ) ) ) { ERROR_GOTO( server_failed, "[RTT] Could not request first block for %s", image->name ); } // See if requesting the block succeeded ++++++++++++++++++++++ @@ -471,6 +472,7 @@ static void *altservers_main(void *data UNUSED) bestSock = sock; bestRtt = avg; bestIndex = itAlt; + bestProtocolVersion = protocolVersion; } else { // Was too slow, ignore close( sock ); @@ -492,6 +494,7 @@ static void *altservers_main(void *data UNUSED) spin_lock( &uplink->rttLock ); uplink->betterFd = bestSock; uplink->betterServer = servers[bestIndex]; + uplink->betterVersion = bestProtocolVersion; uplink->rttTestResult = RTT_DOCHANGE; spin_unlock( &uplink->rttLock ); signal_call( uplink->signal ); diff --git a/src/server/globals.h b/src/server/globals.h index 379fb8d..b1740f4 100644 --- a/src/server/globals.h +++ b/src/server/globals.h @@ -21,7 +21,7 @@ typedef struct _dnbd3_client dnbd3_client_t; // Must only be set in uplink_request() #define ULR_NEW 1 // Slot is occupied, reply has not yet been received, matching request can safely rely on reuse. -// Must only be set in uplink_mainloop() +// Must only be set in uplink_mainloop() or uplink_request() #define ULR_PENDING 2 // Slot is being processed, do not consider for hop on. // Must only be set in uplink_handle_receive() @@ -34,6 +34,7 @@ typedef struct dnbd3_client_t * client; // Client to send reply to int status; // status of this entry: ULR_* time_t entered; // When this request entered the queue (for debugging) + uint8_t hopCount; // How many hops this request has already taken across proxies } dnbd3_queued_request_t; #define RTT_IDLE 0 // Not in progress @@ -44,16 +45,16 @@ typedef struct struct _dnbd3_connection { int fd; // socket fd to remote server + int version; // remote server protocol version dnbd3_signal_t* signal; // used to wake up the process pthread_t thread; // thread holding the connection pthread_spinlock_t queueLock; // lock for synchronization on request queue etc. - dnbd3_queued_request_t queue[SERVER_MAX_UPLINK_QUEUE]; - int queueLen; // length of queue dnbd3_image_t *image; // image that this uplink is used for; do not call get/release for this pointer dnbd3_host_t currentServer; // Current server we're connected to pthread_spinlock_t rttLock; // When accessing rttTestResult, betterFd or betterServer int rttTestResult; // RTT_* dnbd3_host_t betterServer; // The better server + int betterVersion; // protocol version of better server int betterFd; // Active connection to better server, ready to use uint8_t *recvBuffer; // Buffer for receiving payload uint32_t recvBufferLen; // Len of ^^ @@ -62,6 +63,8 @@ struct _dnbd3_connection int nextReplicationIndex; // Which index in the cache map we should start looking for incomplete blocks at uint64_t replicationHandle; // Handle of pending replication request uint64_t bytesReceived; // Number of bytes received by the connection. + int queueLen; // length of queue + dnbd3_queued_request_t queue[SERVER_MAX_UPLINK_QUEUE]; }; typedef struct diff --git a/src/server/image.c b/src/server/image.c index 36e0f0c..78de2bb 100644 --- a/src/server/image.c +++ b/src/server/image.c @@ -430,7 +430,7 @@ dnbd3_image_t* image_get(char *name, uint16_t revision, bool checkIfWorking) } } if ( candidate->uplink == NULL && candidate->cacheFd != -1 ) { - uplink_init( candidate, -1, NULL ); + uplink_init( candidate, -1, NULL, -1 ); } } @@ -916,7 +916,7 @@ static bool image_load(char *base, char *path, int withUplink) goto load_error; } if ( withUplink ) { - uplink_init( image, -1, NULL ); + uplink_init( image, -1, NULL, -1 ); } } @@ -1227,7 +1227,7 @@ server_fail: ; image = image_get( name, remoteRid, false ); if ( image != NULL && uplinkSock != -1 && uplinkServer != NULL ) { // If so, init the uplink and pass it the socket - if ( !uplink_init( image, uplinkSock, uplinkServer ) ) { + if ( !uplink_init( image, uplinkSock, uplinkServer, remoteProtocolVersion ) ) { close( uplinkSock ); } else { // Clumsy busy wait, but this should only take as long as it takes to start a thread, so is it really worth using a signalling mechanism? diff --git a/src/server/net.c b/src/server/net.c index 86be7ef..acb3dbe 100644 --- a/src/server/net.c +++ b/src/server/net.c @@ -294,8 +294,9 @@ void* net_handleNewConnection(void *clientPtr) if ( _shutdown ) break; switch ( request.cmd ) { - case CMD_GET_BLOCK: - if ( request.offset >= image->virtualFilesize ) { + case CMD_GET_BLOCK:; + const uint64_t offset = request.offset_small; // Copy to full uint64 to prevent repeated masking + if ( offset >= image->virtualFilesize ) { // Sanity check logadd( LOG_WARNING, "Client %s requested non-existent block", client->hostName ); reply.size = 0; @@ -303,7 +304,7 @@ void* net_handleNewConnection(void *clientPtr) send_reply( client->sock, &reply, NULL ); break; } - if ( request.offset + request.size > image->virtualFilesize ) { + if ( offset + request.size > image->virtualFilesize ) { // Sanity check logadd( LOG_WARNING, "Client %s requested data block that extends beyond image size", client->hostName ); reply.size = 0; @@ -314,8 +315,8 @@ void* net_handleNewConnection(void *clientPtr) if ( request.size != 0 && image->cache_map != NULL ) { // This is a proxyed image, check if we need to relay the request... - start = request.offset & ~(uint64_t)(DNBD3_BLOCK_SIZE - 1); - end = (request.offset + request.size + DNBD3_BLOCK_SIZE - 1) & ~(uint64_t)(DNBD3_BLOCK_SIZE - 1); + start = offset & ~(uint64_t)(DNBD3_BLOCK_SIZE - 1); + end = (offset + request.size + DNBD3_BLOCK_SIZE - 1) & ~(uint64_t)(DNBD3_BLOCK_SIZE - 1); bool isCached = true; spin_lock( &image->lock ); // Check again as we only aquired the lock just now @@ -364,13 +365,13 @@ void* net_handleNewConnection(void *clientPtr) } spin_unlock( &image->lock ); if ( !isCached ) { - if ( !uplink_request( client, request.handle, request.offset, request.size ) ) { + if ( !uplink_request( client, request.handle, offset, request.size, request.hops ) ) { logadd( LOG_DEBUG1, "Could not relay uncached request from %s to upstream proxy, disabling image %s:%d", client->hostName, image->name, image->rid ); image->working = false; goto exit_client_cleanup; } - break; // DONE + break; // DONE, exit request.cmd switch } } @@ -391,16 +392,16 @@ void* net_handleNewConnection(void *clientPtr) if ( request.size != 0 ) { // Send payload if request length > 0 size_t done = 0; - off_t offset = (off_t)request.offset; + off_t foffset = (off_t)offset; size_t realBytes; - if ( request.offset + request.size <= image->realFilesize ) { + if ( offset + request.size <= image->realFilesize ) { realBytes = request.size; } else { - realBytes = image->realFilesize - request.offset; + realBytes = image->realFilesize - offset; } while ( done < realBytes ) { #ifdef __linux__ - const ssize_t ret = sendfile( client->sock, image_file, &offset, realBytes - done ); + const ssize_t ret = sendfile( client->sock, image_file, &foffset, realBytes - done ); if ( ret <= 0 ) { const int err = errno; if ( lock ) pthread_mutex_unlock( &client->sendMutex ); @@ -420,7 +421,7 @@ void* net_handleNewConnection(void *clientPtr) done += ret; #elif defined(__FreeBSD__) off_t sent; - int ret = sendfile( image_file, client->sock, offset, realBytes - done, NULL, &sent, 0 ); + int ret = sendfile( image_file, client->sock, foffset, realBytes - done, NULL, &sent, 0 ); const int err = errno; if ( ret < 0 ) { if ( err == EAGAIN ) { diff --git a/src/server/uplink.c b/src/server/uplink.c index a8d2f0b..65cefb5 100644 --- a/src/server/uplink.c +++ b/src/server/uplink.c @@ -43,7 +43,7 @@ uint64_t uplink_getTotalBytesReceived() * image. Uplinks run in their own thread. * Locks on: _images[].lock */ -bool uplink_init(dnbd3_image_t *image, int sock, dnbd3_host_t *host) +bool uplink_init(dnbd3_image_t *image, int sock, dnbd3_host_t *host, int version) { if ( !_isProxy ) return false; dnbd3_connection_t *link = NULL; @@ -72,6 +72,7 @@ bool uplink_init(dnbd3_image_t *image, int sock, dnbd3_host_t *host) link->betterFd = sock; link->betterServer = *host; link->rttTestResult = RTT_DOCHANGE; + link->betterVersion = version; } else { link->betterFd = -1; link->rttTestResult = RTT_IDLE; @@ -145,7 +146,7 @@ void uplink_removeClient(dnbd3_connection_t *uplink, dnbd3_client_t *client) * Request a chunk of data through an uplink server * Locks on: image.lock, uplink.queueLock */ -bool uplink_request(dnbd3_client_t *client, uint64_t handle, uint64_t start, uint32_t length) +bool uplink_request(dnbd3_client_t *client, uint64_t handle, uint64_t start, uint32_t length, uint8_t hops) { if ( client == NULL || client->image == NULL ) return false; spin_lock( &client->image->lock ); @@ -161,9 +162,10 @@ bool uplink_request(dnbd3_client_t *client, uint64_t handle, uint64_t start, uin return false; } // Check if the client is the same host as the uplink. If so assume this is a circular proxy chain - if ( isSameAddress( &uplink->currentServer, &client->host ) ) { + // This might be a false positive if there are multiple instances running on the same host (IP) + if ( hops != 0 && isSameAddress( &uplink->currentServer, &client->host ) ) { spin_unlock( &client->image->lock ); - logadd( LOG_DEBUG1, "Proxy cycle detected" ); + logadd( LOG_WARNING, "Proxy cycle detected (same host)." ); return false; } @@ -171,19 +173,34 @@ bool uplink_request(dnbd3_client_t *client, uint64_t handle, uint64_t start, uin int existingType = -1; // ULR_* type of existing request int i; int freeSlot = -1; + bool requestLoop = false; const uint64_t end = start + length; spin_lock( &uplink->queueLock ); spin_unlock( &client->image->lock ); for (i = 0; i < uplink->queueLen; ++i) { - if ( freeSlot == -1 && uplink->queue[i].status == ULR_FREE ) freeSlot = i; + if ( freeSlot == -1 && uplink->queue[i].status == ULR_FREE ) { + freeSlot = i; + continue; + } if ( uplink->queue[i].status != ULR_PENDING && uplink->queue[i].status != ULR_NEW ) continue; - if ( (foundExisting == -1 || existingType == ULR_PENDING) && uplink->queue[i].from <= start && uplink->queue[i].to >= end ) { - foundExisting = i; - existingType = uplink->queue[i].status; - break; + if ( uplink->queue[i].from <= start && uplink->queue[i].to >= end ) { + if ( hops > uplink->queue[i].hopCount ) { + requestLoop = true; + break; + } + if ( foundExisting == -1 || existingType == ULR_PENDING ) { + foundExisting = i; + existingType = uplink->queue[i].status; + if ( freeSlot != -1 ) break; + } } } + if ( requestLoop ) { + spin_unlock( &uplink->queueLock ); + logadd( LOG_WARNING, "Rejecting relay of request to upstream proxy because of possible cyclic proxy chain. Incoming hop-count is %" PRIu8 ".", hops ); + return false; + } if ( freeSlot == -1 ) { if ( uplink->queueLen >= SERVER_MAX_UPLINK_QUEUE ) { spin_unlock( &uplink->queueLock ); @@ -198,7 +215,7 @@ bool uplink_request(dnbd3_client_t *client, uint64_t handle, uint64_t start, uin // a race condition where the reply for the outstanding request already arrived and the uplink thread // is currently traversing the request queue. As it is processing the queue from highest to lowest index, it might // already have passed the index of the free slot we determined, but not reached the existing request we just found above. - if ( foundExisting != -1 && existingType != ULR_NEW && freeSlot > foundExisting ) foundExisting = -1; + if ( foundExisting != -1 && existingType != ULR_NEW && freeSlot > foundExisting ) foundExisting = -1; // -1 means "send request" #ifdef _DEBUG if ( foundExisting != -1 ) { logadd( LOG_DEBUG2, "%p (%s) Found existing request of type %s at slot %d, attaching in slot %d.\n", (void*)uplink, uplink->image->name, existingType == ULR_NEW ? "ULR_NEW" : "ULR_PENDING", foundExisting, freeSlot ); @@ -215,6 +232,7 @@ bool uplink_request(dnbd3_client_t *client, uint64_t handle, uint64_t start, uin uplink->queue[freeSlot].client = client; //int old = uplink->queue[freeSlot].status; uplink->queue[freeSlot].status = (foundExisting == -1 ? ULR_NEW : ULR_PENDING); + uplink->queue[freeSlot].hopCount = hops; #ifdef _DEBUG uplink->queue[freeSlot].entered = time( NULL ); //logadd( LOG_DEBUG2 %p] Inserting request at slot %d, was %d, now %d, handle %" PRIu64 ", Range: %" PRIu64 "-%" PRIu64 "\n", (void*)uplink, freeSlot, old, uplink->queue[freeSlot].status, uplink->queue[freeSlot, ".handle, start, end ); @@ -272,6 +290,7 @@ static void* uplink_mainloop(void *data) link->fd = link->betterFd; link->betterFd = -1; link->currentServer = link->betterServer; + link->version = link->betterVersion; spin_unlock( &link->rttLock ); discoverFailCount = 0; if ( fd != -1 ) close( fd ); @@ -439,8 +458,10 @@ static void uplink_sendRequests(dnbd3_connection_t *link, bool newOnly) link->queue[j].status = ULR_PENDING; const uint64_t offset = link->queue[j].from; const uint32_t size = link->queue[j].to - link->queue[j].from; + uint8_t hops = link->queue[j].hopCount; spin_unlock( &link->queueLock ); - const int ret = dnbd3_get_block( link->fd, offset, size, offset ); + if ( hops < 200 ) hops += 1; + const int ret = dnbd3_get_block( link->fd, offset, size, offset, COND_HOPCOUNT( link->version, hops ) ); if ( !ret ) { // Non-critical - if the connection dropped or the server was changed // the thread will re-send this request as soon as the connection @@ -488,7 +509,7 @@ static void uplink_sendReplicationRequest(dnbd3_connection_t *link) // Unlocked - do not break or continue here... const uint64_t offset = link->replicationHandle = (uint64_t)i * (uint64_t)requestBlockSize; const uint32_t size = MIN( image->realFilesize - offset, requestBlockSize ); - if ( !dnbd3_get_block( link->fd, offset, size, link->replicationHandle ) ) { + if ( !dnbd3_get_block( link->fd, offset, size, link->replicationHandle, COND_HOPCOUNT( link->version, 1 ) ) ) { logadd( LOG_DEBUG1, "Error sending background replication request to uplink server!\n" ); return; } @@ -644,7 +665,7 @@ static void uplink_handleReceive(dnbd3_connection_t *link) */ static int uplink_sendKeepalive(const int fd) { - static dnbd3_request_t request = { 0, 0, 0, 0, 0 }; + static dnbd3_request_t request = { 0 }; if ( request.magic == 0 ) { request.magic = dnbd3_packet_magic; request.cmd = CMD_KEEPALIVE; diff --git a/src/server/uplink.h b/src/server/uplink.h index c8cf4eb..2b41dfc 100644 --- a/src/server/uplink.h +++ b/src/server/uplink.h @@ -8,11 +8,11 @@ void uplink_globalsInit(); uint64_t uplink_getTotalBytesReceived(); -bool uplink_init(dnbd3_image_t *image, int sock, dnbd3_host_t *host); +bool uplink_init(dnbd3_image_t *image, int sock, dnbd3_host_t *host, int version); void uplink_removeClient(dnbd3_connection_t *uplink, dnbd3_client_t *client); -bool uplink_request(dnbd3_client_t *client, uint64_t handle, uint64_t start, uint32_t length); +bool uplink_request(dnbd3_client_t *client, uint64_t handle, uint64_t start, uint32_t length, uint8_t hopCount); void uplink_shutdown(dnbd3_image_t *image); diff --git a/src/shared/protocol.h b/src/shared/protocol.h index eb1178d..c3ccbc1 100644 --- a/src/shared/protocol.h +++ b/src/shared/protocol.h @@ -13,6 +13,9 @@ #define FLAGS8_SERVER (1) +// 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 ) + #define REPLY_OK (0) #define REPLY_ERRNO (-1) #define REPLY_AGAIN (-2) @@ -76,13 +79,16 @@ static inline bool dnbd3_select_image(int sock, const char *name, uint16_t rid, return ret == len + (ssize_t)sizeof(request); } -static inline bool dnbd3_get_block(int sock, uint64_t offset, uint32_t size, uint64_t handle) +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); diff --git a/src/types.h b/src/types.h index 63fe3a6..9f9e744 100644 --- a/src/types.h +++ b/src/types.h @@ -37,7 +37,7 @@ #ifdef __GNUC__ #define UNUSED __attribute__ ((unused)) #else -#define UNUSED dfg dsfg dg +#error "Please add define for your compiler for UNUSED, or define to nothing for your compiler if not supported" #endif #ifdef __linux__ @@ -79,6 +79,9 @@ static const uint16_t dnbd3_packet_magic = (0x73 << 8) | (0x72); (a).size = net_order_32((a).size); \ } while (0) #define ENDIAN_MODE "Big Endian" +#ifndef BIG_ENDIAN +#define BIG_ENDIAN +#endif #elif defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(__i386__) || defined(__i386) || defined(__x86_64) static const uint16_t dnbd3_packet_magic = (0x73) | (0x72 << 8); // Make little endian our network byte order as probably 99.999% of machines this will be used on are LE @@ -88,6 +91,9 @@ static const uint16_t dnbd3_packet_magic = (0x73) | (0x72 << 8); #define fixup_request(a) while(0) #define fixup_reply(a) while(0) #define ENDIAN_MODE "Little Endian" +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN +#endif #else #error "Unknown Endianness" #endif @@ -124,17 +130,31 @@ typedef struct #define CMD_SET_CLIENT_MODE 7 #define CMD_GET_CRC32 8 +#define DNBD3_REQUEST_SIZE 24 #pragma pack(1) typedef struct { - uint16_t magic; // 2byte - uint16_t cmd; // 2byte - uint32_t size; // 4byte - uint64_t offset; // 8byte - uint64_t handle; // 8byte + uint16_t magic; // 2byte + uint16_t cmd; // 2byte + uint32_t size; // 4byte + union { + struct { +#ifdef LITTLE_ENDIAN + uint64_t offset_small:56; // 7byte + uint8_t hops; // 1byte +#elif defined(BIG_ENDIAN) + uint8_t hops; // 1byte + uint64_t offset_small:56; // 7byte +#endif + }; + uint64_t offset; // 8byte + }; + uint64_t handle; // 8byte } dnbd3_request_t; #pragma pack(0) +_Static_assert( sizeof(dnbd3_request_t) == DNBD3_REQUEST_SIZE, "dnbd3_request_t is messed up" ); +#define DNBD3_REPLY_SIZE 16 #pragma pack(1) typedef struct { @@ -144,6 +164,7 @@ typedef struct uint64_t handle; // 8byte } dnbd3_reply_t; #pragma pack(0) +_Static_assert( sizeof(dnbd3_reply_t) == DNBD3_REPLY_SIZE, "dnbd3_reply_t is messed up" ); #pragma pack(1) typedef struct |