iscsi-md5sum: Add new tool

The new tool 'iscsi-md5sum' is used to calculate MD5 value of an iSCSI
target. This help users to verify data at range [LBA, Length).

For example, double-write on a RAID1 of 2 iSCSI targets, a daemon
process runs iscsi-md5sum to check data periodically.

Originally, we have to use several steps to achieve:
1, use iscsiadm to login.(root privilege required)
2, use dd(dd if=/dev/sdX of=/TMPPATH bs=4k count=LENGTH skip=LBA)
   to dump data. (root privilege required, additional disk space required)
3, use md5sum to calculate MD5 value
4, remove data.

Instead, a single command iscsi-md5sum without root privilege is enough.

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
This commit is contained in:
zhenwei pi
2023-07-28 18:34:10 +08:00
parent 9e9a3e8198
commit 45b274aec8
2 changed files with 596 additions and 1 deletions

View File

@@ -3,7 +3,7 @@ AM_CFLAGS = $(WARN_CFLAGS)
AM_LDFLAGS = -no-undefined
LIBS = ../lib/libiscsi.la
bin_PROGRAMS = iscsi-inq iscsi-ls iscsi-swp iscsi-pr iscsi-discard
bin_PROGRAMS = iscsi-inq iscsi-ls iscsi-swp iscsi-pr iscsi-discard iscsi-md5sum
if !TARGET_OS_IS_WIN32
bin_PROGRAMS += iscsi-perf iscsi-readcapacity16
endif

595
utils/iscsi-md5sum.c Normal file
View File

@@ -0,0 +1,595 @@
/*
Copyright (C) 2023 by zhenwei pi <pizhenwei@bytedance.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <poll.h>
#include <getopt.h>
#include "iscsi.h"
#include "scsi-lowlevel.h"
/* MD5 related codes come from glibc with a few change(to avoid symbol conflict) */
# if __BYTE_ORDER == __BIG_ENDIAN
# define WORDS_BIGENDIAN 1
# endif
#ifdef WORDS_BIGENDIAN
# define SWAP(n) \
(((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
#else
# define SWAP(n) (n)
#endif
typedef unsigned int libiscsi_md5_uint32;
struct libiscsi_md5_ctx {
libiscsi_md5_uint32 A;
libiscsi_md5_uint32 B;
libiscsi_md5_uint32 C;
libiscsi_md5_uint32 D;
libiscsi_md5_uint32 total[2];
libiscsi_md5_uint32 buflen;
union
{
char buffer[128];
libiscsi_md5_uint32 buffer32[32];
};
};
/* These are the four functions used in the four steps of the MD5 algorithm
and defined in the RFC 1321. The first function is a little bit optimized
(as found in Colin Plumbs public domain implementation). */
/* #define FF(b, c, d) ((b & c) | (~b & d)) */
#define FF(b, c, d) (d ^ (b & (c ^ d)))
#define FG(b, c, d) FF (d, b, c)
#define FH(b, c, d) (b ^ c ^ d)
#define FI(b, c, d) (c ^ (b | ~d))
/* Process LEN bytes of BUFFER, accumulating context into CTX.
It is assumed that LEN % 64 == 0. */
static void
libiscsi_md5_process_block (const void *buffer, size_t len, struct libiscsi_md5_ctx *ctx)
{
libiscsi_md5_uint32 correct_words[16];
const libiscsi_md5_uint32 *words = buffer;
size_t nwords = len / sizeof (libiscsi_md5_uint32);
const libiscsi_md5_uint32 *endp = words + nwords;
libiscsi_md5_uint32 A = ctx->A;
libiscsi_md5_uint32 B = ctx->B;
libiscsi_md5_uint32 C = ctx->C;
libiscsi_md5_uint32 D = ctx->D;
libiscsi_md5_uint32 lolen = len;
/* First increment the byte count. RFC 1321 specifies the possible
length of the file up to 2^64 bits. Here we only compute the
number of bytes. Do a double word increment. */
ctx->total[0] += lolen;
ctx->total[1] += (len >> 31 >> 1) + (ctx->total[0] < lolen);
/* Process all bytes in the buffer with 64 bytes in each round of
the loop. */
while (words < endp)
{
libiscsi_md5_uint32 *cwp = correct_words;
libiscsi_md5_uint32 A_save = A;
libiscsi_md5_uint32 B_save = B;
libiscsi_md5_uint32 C_save = C;
libiscsi_md5_uint32 D_save = D;
/* First round: using the given function, the context and a constant
the next context is computed. Because the algorithms processing
unit is a 32-bit word and it is determined to work on words in
little endian byte order we perhaps have to change the byte order
before the computation. To reduce the work for the next steps
we store the swapped words in the array CORRECT_WORDS. */
#define OP(a, b, c, d, s, T) \
do \
{ \
a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \
++words; \
CYCLIC (a, s); \
a += b; \
} \
while (0)
/* It is unfortunate that C does not provide an operator for
cyclic rotation. Hope the C compiler is smart enough. */
#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
/* Before we start, one word to the strange constants.
They are defined in RFC 1321 as
T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
*/
/* Round 1. */
OP (A, B, C, D, 7, 0xd76aa478);
OP (D, A, B, C, 12, 0xe8c7b756);
OP (C, D, A, B, 17, 0x242070db);
OP (B, C, D, A, 22, 0xc1bdceee);
OP (A, B, C, D, 7, 0xf57c0faf);
OP (D, A, B, C, 12, 0x4787c62a);
OP (C, D, A, B, 17, 0xa8304613);
OP (B, C, D, A, 22, 0xfd469501);
OP (A, B, C, D, 7, 0x698098d8);
OP (D, A, B, C, 12, 0x8b44f7af);
OP (C, D, A, B, 17, 0xffff5bb1);
OP (B, C, D, A, 22, 0x895cd7be);
OP (A, B, C, D, 7, 0x6b901122);
OP (D, A, B, C, 12, 0xfd987193);
OP (C, D, A, B, 17, 0xa679438e);
OP (B, C, D, A, 22, 0x49b40821);
/* For the second to fourth round we have the possibly swapped words
in CORRECT_WORDS. Redefine the macro to take an additional first
argument specifying the function to use. */
#undef OP
#define OP(f, a, b, c, d, k, s, T) \
do \
{ \
a += f (b, c, d) + correct_words[k] + T; \
CYCLIC (a, s); \
a += b; \
} \
while (0)
/* Round 2. */
OP (FG, A, B, C, D, 1, 5, 0xf61e2562);
OP (FG, D, A, B, C, 6, 9, 0xc040b340);
OP (FG, C, D, A, B, 11, 14, 0x265e5a51);
OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa);
OP (FG, A, B, C, D, 5, 5, 0xd62f105d);
OP (FG, D, A, B, C, 10, 9, 0x02441453);
OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);
OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8);
OP (FG, A, B, C, D, 9, 5, 0x21e1cde6);
OP (FG, D, A, B, C, 14, 9, 0xc33707d6);
OP (FG, C, D, A, B, 3, 14, 0xf4d50d87);
OP (FG, B, C, D, A, 8, 20, 0x455a14ed);
OP (FG, A, B, C, D, 13, 5, 0xa9e3e905);
OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8);
OP (FG, C, D, A, B, 7, 14, 0x676f02d9);
OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
/* Round 3. */
OP (FH, A, B, C, D, 5, 4, 0xfffa3942);
OP (FH, D, A, B, C, 8, 11, 0x8771f681);
OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);
OP (FH, B, C, D, A, 14, 23, 0xfde5380c);
OP (FH, A, B, C, D, 1, 4, 0xa4beea44);
OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9);
OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60);
OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);
OP (FH, A, B, C, D, 13, 4, 0x289b7ec6);
OP (FH, D, A, B, C, 0, 11, 0xeaa127fa);
OP (FH, C, D, A, B, 3, 16, 0xd4ef3085);
OP (FH, B, C, D, A, 6, 23, 0x04881d05);
OP (FH, A, B, C, D, 9, 4, 0xd9d4d039);
OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);
OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);
OP (FH, B, C, D, A, 2, 23, 0xc4ac5665);
/* Round 4. */
OP (FI, A, B, C, D, 0, 6, 0xf4292244);
OP (FI, D, A, B, C, 7, 10, 0x432aff97);
OP (FI, C, D, A, B, 14, 15, 0xab9423a7);
OP (FI, B, C, D, A, 5, 21, 0xfc93a039);
OP (FI, A, B, C, D, 12, 6, 0x655b59c3);
OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92);
OP (FI, C, D, A, B, 10, 15, 0xffeff47d);
OP (FI, B, C, D, A, 1, 21, 0x85845dd1);
OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f);
OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
OP (FI, C, D, A, B, 6, 15, 0xa3014314);
OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);
OP (FI, A, B, C, D, 4, 6, 0xf7537e82);
OP (FI, D, A, B, C, 11, 10, 0xbd3af235);
OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb);
OP (FI, B, C, D, A, 9, 21, 0xeb86d391);
/* Add the starting values of the context. */
A += A_save;
B += B_save;
C += C_save;
D += D_save;
}
/* Put checksum in context given as argument. */
ctx->A = A;
ctx->B = B;
ctx->C = C;
ctx->D = D;
}
/* This array contains the bytes used to pad the buffer to the next
64-byte boundary. (RFC 1321, 3.1: Step 1) */
static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
/* Initialize structure containing state of computation.
(RFC 1321, 3.3: Step 3) */
static void
libiscsi_md5_init_ctx (struct libiscsi_md5_ctx *ctx)
{
ctx->A = 0x67452301;
ctx->B = 0xefcdab89;
ctx->C = 0x98badcfe;
ctx->D = 0x10325476;
ctx->total[0] = ctx->total[1] = 0;
ctx->buflen = 0;
}
/* Put result from CTX in first 16 bytes following RESBUF. The result
must be in little endian byte order.
IMPORTANT: On some systems it is required that RESBUF is correctly
aligned for a 32 bits value. */
static void *
libiscsi_md5_read_ctx (const struct libiscsi_md5_ctx *ctx, void *resbuf)
{
((libiscsi_md5_uint32 *) resbuf)[0] = SWAP (ctx->A);
((libiscsi_md5_uint32 *) resbuf)[1] = SWAP (ctx->B);
((libiscsi_md5_uint32 *) resbuf)[2] = SWAP (ctx->C);
((libiscsi_md5_uint32 *) resbuf)[3] = SWAP (ctx->D);
return resbuf;
}
/* Process the remaining bytes in the internal buffer and the usual
prolog according to the standard and write the result to RESBUF.
IMPORTANT: On some systems it is required that RESBUF is correctly
aligned for a 32 bits value. */
static void *
libiscsi_md5_finish_ctx (struct libiscsi_md5_ctx *ctx, void *resbuf)
{
/* Take yet unprocessed bytes into account. */
libiscsi_md5_uint32 bytes = ctx->buflen;
size_t pad;
/* Now count remaining bytes. */
ctx->total[0] += bytes;
if (ctx->total[0] < bytes)
++ctx->total[1];
pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
memcpy (&ctx->buffer[bytes], fillbuf, pad);
/* Put the 64-bit file length in *bits* at the end of the buffer. */
ctx->buffer32[(bytes + pad) / 4] = SWAP (ctx->total[0] << 3);
ctx->buffer32[(bytes + pad + 4) / 4] = SWAP ((ctx->total[1] << 3)
| (ctx->total[0] >> 29));
/* Process last bytes. */
libiscsi_md5_process_block (ctx->buffer, bytes + pad + 8, ctx);
return libiscsi_md5_read_ctx (ctx, resbuf);
}
static void
libiscsi_md5_process_bytes (const void *buffer, size_t len, struct libiscsi_md5_ctx *ctx)
{
/* When we already have some bits in our internal buffer concatenate
both inputs first. */
if (ctx->buflen != 0)
{
size_t left_over = ctx->buflen;
size_t add = 128 - left_over > len ? len : 128 - left_over;
memcpy (&ctx->buffer[left_over], buffer, add);
ctx->buflen += add;
if (ctx->buflen > 64)
{
libiscsi_md5_process_block (ctx->buffer, ctx->buflen & ~63, ctx);
ctx->buflen &= 63;
/* The regions in the following copy operation cannot overlap. */
memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
ctx->buflen);
}
buffer = (const char *) buffer + add;
len -= add;
}
/* Process available complete blocks. */
if (len >= 64)
{
while (len > 64)
{
libiscsi_md5_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx);
buffer = (const char *) buffer + 64;
len -= 64;
}
}
/* Move remaining bytes in internal buffer. */
if (len > 0)
{
size_t left_over = ctx->buflen;
memcpy (&ctx->buffer[left_over], buffer, len);
left_over += len;
if (left_over >= 64)
{
libiscsi_md5_process_block (ctx->buffer, 64, ctx);
left_over -= 64;
memcpy (ctx->buffer, &ctx->buffer[64], left_over);
}
ctx->buflen = left_over;
}
}
/* iSCSI codes */
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
static const char *initiator = "iqn.2007-10.com.github:sahlberg:libiscsi:iscsi-md5sum";
static void print_help(const char *prog)
{
fprintf(stderr, "Usage: %s [OPTION...] <iscsi-url>\n", prog);
fprintf(stderr, " -i, --initiator-name=iqn-name Initiatorname to use\n");
fprintf(stderr, " -o, --offset "
"Byte offset into the target from which to start calculating. "
"The provided value must be aligned to the target sector size. "
"The default value is zero.\n");
fprintf(stderr, " -l, --length "
"The number of bytes to calculate (counting from the starting point). "
"The provided value must be aligned to the target sector size. "
"If the specified value extends past the end of the device, "
"%s will stop at the device size boundary. "
"The default value extends to the end of the device.\n", prog);
fprintf(stderr, " -d, --debug=integer debug level (0=disabled)\n");
fprintf(stderr, "\n");
fprintf(stderr, "Help options:\n");
fprintf(stderr, " -?, --help Show this help message\n");
fprintf(stderr, "\n");
fprintf(stderr, "iSCSI URL format : %s\n", ISCSI_URL_SYNTAX);
fprintf(stderr, "\n");
fprintf(stderr, "<host> is either of:\n");
fprintf(stderr, " \"hostname\" iscsi.example\n");
fprintf(stderr, " \"ipv4-address\" 10.1.1.27\n");
fprintf(stderr, " \"ipv6-address\" [fce0::1]\n");
exit(0);
}
void inquiry_block_limits(struct scsi_inquiry_block_limits *inq)
{
printf("wsnz:%d\n", inq->wsnz);
printf("maximum compare and write length:%" PRIu8 "\n", inq->max_cmp);
printf("optimal transfer length granularity:%" PRIu16 "\n", inq->opt_gran);
printf("maximum transfer length:%" PRIu32 "\n", inq->max_xfer_len);
printf("optimal transfer length:%" PRIu32 "\n",inq->opt_xfer_len);
printf("maximum prefetch xdread xdwrite transfer length:%" PRIu32 "\n", inq->max_prefetch);
printf("maximum unmap lba count:%" PRIu32 "\n", inq->max_unmap);
printf("maximum unmap block descriptor count:%" PRIu32 "\n", inq->max_unmap_bdc);
printf("optimal unmap granularity:%" PRIu32 "\n", inq->opt_unmap_gran);
printf("ugavalid:%d\n", inq->ugavalid);
printf("unmap granularity alignment:%" PRIu32 "\n", inq->unmap_gran_align);
printf("maximum write same length:%" PRIu64 "\n", inq->max_ws_len);
}
static unsigned long inquiry_xfer_len(struct iscsi_context *iscsi, int lun, unsigned int block_length)
{
struct scsi_task *task;
int full_size;
struct scsi_inquiry_block_limits *inq;
unsigned long max_xfer_len = 1024 * 1024; /* default size 1M */
/* See how big this inquiry data is */
task = iscsi_inquiry_sync(iscsi, lun, 1, SCSI_INQUIRY_PAGECODE_BLOCK_LIMITS, 64);
if (task == NULL || task->status != SCSI_STATUS_GOOD) {
fprintf(stderr, "Inquiry command failed : %s\n", iscsi_get_error(iscsi));
exit(EIO);
}
full_size = scsi_datain_getfullsize(task);
if (full_size > task->datain.size) {
scsi_free_scsi_task(task);
/* we need more data for the full list */
if ((task = iscsi_inquiry_sync(iscsi, lun, 1, SCSI_INQUIRY_PAGECODE_BLOCK_LIMITS, full_size)) == NULL) {
fprintf(stderr, "Inquiry command failed : %s\n", iscsi_get_error(iscsi));
exit(EIO);
}
}
inq = scsi_datain_unmarshall(task);
if (inq == NULL) {
fprintf(stderr, "failed to unmarshall inquiry datain blob\n");
exit(EIO);
}
if (inq->max_xfer_len)
max_xfer_len = MIN(max_xfer_len, inq->max_xfer_len * block_length);
scsi_free_scsi_task(task);
return max_xfer_len;
}
static void pread16(struct iscsi_context *iscsi, int lun, void *buf, uint64_t lba, uint32_t datalen, unsigned int block_size)
{
struct scsi_iovec iov = { .iov_base = buf, .iov_len = datalen };
struct scsi_task *task;
task = iscsi_read16_iov_sync(iscsi, lun, lba, datalen, block_size, 0, 0, 0, 0, 0, &iov, 1);
if (task == NULL || task->status != SCSI_STATUS_GOOD) {
fprintf(stderr, "read16 command failed : %s\n", iscsi_get_error(iscsi));
exit(EIO);
}
scsi_free_scsi_task(task);
}
int main(int argc, char *argv[])
{
struct iscsi_context *iscsi;
char *url = NULL;
struct iscsi_url *iscsi_url = NULL;
int debug = 0;
int option_index, c;
unsigned int block_length;
long long offset = 0, length = 0, capacity, max_xfer_len, end;
struct scsi_task *task;
struct scsi_readcapacity16 *rc16;
struct libiscsi_md5_ctx ctx;
unsigned char sum[16];
unsigned char *buf;
int ret = EINVAL;
static struct option long_options[] = {
{"offset", required_argument, NULL, 'o'},
{"length", required_argument, NULL, 'l'},
{"debug", required_argument, NULL, 'd'},
{"help", no_argument, NULL, 'h'},
{"initiator-name", required_argument, NULL, 'i'},
{0, 0, 0, 0}
};
while ((c = getopt_long(argc, argv, "o:l:d:i:h?", long_options,
&option_index)) != -1) {
switch (c) {
case 'o':
offset = strtoll(optarg, NULL, 0);
break;
case 'l':
length = strtoll(optarg, NULL, 0);
break;
case 'd':
debug = strtol(optarg, NULL, 0);
break;
case 'i':
initiator = optarg;
break;
case 'h':
case '?':
print_help(argv[0]);
break;
default:
fprintf(stderr, "Unrecognized option '%c'\n\n", c);
print_help(argv[0]);
break;
}
}
iscsi = iscsi_create_context(initiator);
if (iscsi == NULL) {
fprintf(stderr, "Failed to create context\n");
exit(EINVAL);
}
if (debug > 0) {
iscsi_set_log_fn(iscsi, iscsi_log_to_stderr);
iscsi_set_log_level(iscsi, debug);
}
if (argv[optind] != NULL) {
url = strdup(argv[optind]);
}
if (url == NULL) {
fprintf(stderr, "You must specify the URL\n");
print_help(argv[0]);
}
iscsi_url = iscsi_parse_full_url(iscsi, url);
free(url);
if (iscsi_url == NULL) {
fprintf(stderr, "Failed to parse URL: %s\n",
iscsi_get_error(iscsi));
exit(EINVAL);
}
iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL);
iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);
if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) != 0) {
fprintf(stderr, "Login Failed. %s\n", iscsi_get_error(iscsi));
goto out;
}
task = iscsi_readcapacity16_sync(iscsi, iscsi_url->lun);
if (task == NULL || task->status != SCSI_STATUS_GOOD) {
fprintf(stderr,"Failed to send readcapacity command\n");
goto out;
}
rc16 = scsi_datain_unmarshall(task);
if (rc16 == NULL) {
fprintf(stderr,"Failed to unmarshall readcapacity16 data\n");
goto out;
}
block_length = rc16->block_length;
if (offset & (block_length - 1)) {
fprintf(stderr,"Unaligned offset of %u\n", block_length);
goto free_task;
}
capacity = block_length * (rc16->returned_lba + 1);
if (offset > capacity) {
fprintf(stderr,"Offset(%lld) exceeds capacity(%lld)\n", offset, capacity);
goto free_task;
}
if (!length || (offset + length > capacity)) {
length = block_length * (rc16->returned_lba + 1) - offset;
}
/* free readcapacity16 task */
scsi_free_scsi_task(task);
libiscsi_md5_init_ctx(&ctx);
max_xfer_len = inquiry_xfer_len(iscsi, iscsi_url->lun, block_length);
buf = calloc(1, max_xfer_len);
for (end = offset + length; offset < end; offset += max_xfer_len) {
long long _len = MIN(end - offset, max_xfer_len);
pread16(iscsi, iscsi_url->lun, buf, offset / block_length, _len, block_length);
libiscsi_md5_process_bytes(buf, _len, &ctx);
}
libiscsi_md5_finish_ctx(&ctx, sum);
free(buf);
/* show MD5 sum in HEX */
for (size_t i = 0; i < sizeof(sum); i++)
printf("%02x", sum[i]);
printf("\n");
ret = 0;
goto out;
free_task:
scsi_free_scsi_task(task);
out:
iscsi_destroy_url(iscsi_url);
iscsi_logout_sync(iscsi);
iscsi_destroy_context(iscsi);
return ret;
}