diff --git a/libknet/nsscrypto.c b/libknet/nsscrypto.c index cca1107d..4cf12833 100644 --- a/libknet/nsscrypto.c +++ b/libknet/nsscrypto.c @@ -1,597 +1,599 @@ #include "config.h" #include #include #include #include #include #include #include "nsscrypto.h" #include "libknet-private.h" #ifdef CRYPTO_DEBUG #define log_printf(format, args...) fprintf(stderr, format "\n", ##args); #else #define log_printf(format, args...); #endif /* * crypto definitions and conversion tables */ #define SALT_SIZE 16 #define KNET_DATABUFSIZE_CRYPT KNET_DATABUFSIZE * 2 enum crypto_crypt_t { CRYPTO_CIPHER_TYPE_NONE = 0, CRYPTO_CIPHER_TYPE_AES256 = 1 }; CK_MECHANISM_TYPE cipher_to_nss[] = { 0, /* CRYPTO_CIPHER_TYPE_NONE */ CKM_AES_CBC_PAD /* CRYPTO_CIPHER_TYPE_AES256 */ }; size_t cipher_key_len[] = { 0, /* CRYPTO_CIPHER_TYPE_NONE */ 32, /* CRYPTO_CIPHER_TYPE_AES256 */ }; size_t cypher_block_len[] = { 0, /* CRYPTO_CIPHER_TYPE_NONE */ AES_BLOCK_SIZE /* CRYPTO_CIPHER_TYPE_AES256 */ }; /* * hash definitions and conversion tables */ enum crypto_hash_t { CRYPTO_HASH_TYPE_NONE = 0, CRYPTO_HASH_TYPE_MD5 = 1, CRYPTO_HASH_TYPE_SHA1 = 2, CRYPTO_HASH_TYPE_SHA256 = 3, CRYPTO_HASH_TYPE_SHA384 = 4, CRYPTO_HASH_TYPE_SHA512 = 5 }; CK_MECHANISM_TYPE hash_to_nss[] = { 0, /* CRYPTO_HASH_TYPE_NONE */ CKM_MD5_HMAC, /* CRYPTO_HASH_TYPE_MD5 */ CKM_SHA_1_HMAC, /* CRYPTO_HASH_TYPE_SHA1 */ CKM_SHA256_HMAC, /* CRYPTO_HASH_TYPE_SHA256 */ CKM_SHA384_HMAC, /* CRYPTO_HASH_TYPE_SHA384 */ CKM_SHA512_HMAC /* CRYPTO_HASH_TYPE_SHA512 */ }; size_t hash_len[] = { 0, /* CRYPTO_HASH_TYPE_NONE */ MD5_LENGTH, /* CRYPTO_HASH_TYPE_MD5 */ SHA1_LENGTH, /* CRYPTO_HASH_TYPE_SHA1 */ SHA256_LENGTH, /* CRYPTO_HASH_TYPE_SHA256 */ SHA384_LENGTH, /* CRYPTO_HASH_TYPE_SHA384 */ SHA512_LENGTH /* CRYPTO_HASH_TYPE_SHA512 */ }; size_t hash_block_len[] = { 0, /* CRYPTO_HASH_TYPE_NONE */ MD5_BLOCK_LENGTH, /* CRYPTO_HASH_TYPE_MD5 */ SHA1_BLOCK_LENGTH, /* CRYPTO_HASH_TYPE_SHA1 */ SHA256_BLOCK_LENGTH, /* CRYPTO_HASH_TYPE_SHA256 */ SHA384_BLOCK_LENGTH, /* CRYPTO_HASH_TYPE_SHA384 */ SHA512_BLOCK_LENGTH /* CRYPTO_HASH_TYPE_SHA512 */ }; struct crypto_instance { PK11SymKey *nss_sym_key; PK11SymKey *nss_sym_key_sign; unsigned char *private_key; unsigned int private_key_len; - enum crypto_crypt_t crypto_cipher_type; + int crypto_cipher_type; - enum crypto_hash_t crypto_hash_type; + int crypto_hash_type; }; /* * crypt/decrypt functions */ static int string_to_crypto_cipher_type(const char* crypto_cipher_type) { if (strcmp(crypto_cipher_type, "none") == 0) { return CRYPTO_CIPHER_TYPE_NONE; } else if (strcmp(crypto_cipher_type, "aes256") == 0) { return CRYPTO_CIPHER_TYPE_AES256; } return -1; } static int init_nss_crypto(struct crypto_instance *instance) { PK11SlotInfo* crypt_slot = NULL; SECItem crypt_param; if (!cipher_to_nss[instance->crypto_cipher_type]) { return 0; } crypt_param.type = siBuffer; crypt_param.data = instance->private_key; crypt_param.len = cipher_key_len[instance->crypto_cipher_type]; crypt_slot = PK11_GetBestSlot(cipher_to_nss[instance->crypto_cipher_type], NULL); if (crypt_slot == NULL) { log_printf("Unable to find security slot (err %d)", PR_GetError()); return -1; } instance->nss_sym_key = PK11_ImportSymKey(crypt_slot, cipher_to_nss[instance->crypto_cipher_type], PK11_OriginUnwrap, CKA_ENCRYPT|CKA_DECRYPT, &crypt_param, NULL); if (instance->nss_sym_key == NULL) { log_printf("Failure to import key into NSS (err %d)", PR_GetError()); return -1; } PK11_FreeSlot(crypt_slot); return 0; } static int encrypt_nss( struct crypto_instance *instance, const unsigned char *buf_in, const size_t buf_in_len, unsigned char *buf_out, size_t *buf_out_len) { PK11Context* crypt_context = NULL; SECItem crypt_param; SECItem *nss_sec_param = NULL; int tmp1_outlen = 0; unsigned int tmp2_outlen = 0; unsigned char *salt = buf_out; unsigned char *data = buf_out + SALT_SIZE; int err = -1; if (PK11_GenerateRandom (salt, SALT_SIZE) != SECSuccess) { log_printf("Failure to generate a random number %d", PR_GetError()); goto out; } crypt_param.type = siBuffer; crypt_param.data = salt; crypt_param.len = SALT_SIZE; nss_sec_param = PK11_ParamFromIV (cipher_to_nss[instance->crypto_cipher_type], &crypt_param); if (nss_sec_param == NULL) { log_printf("Failure to set up PKCS11 param (err %d)", PR_GetError()); goto out; } /* * Create cipher context for encryption */ crypt_context = PK11_CreateContextBySymKey (cipher_to_nss[instance->crypto_cipher_type], CKA_ENCRYPT, instance->nss_sym_key, nss_sec_param); if (!crypt_context) { log_printf("PK11_CreateContext failed (encrypt) crypt_type=%d (err %d)", (int)cipher_to_nss[instance->crypto_cipher_type], PR_GetError()); goto out; } if (PK11_CipherOp(crypt_context, data, &tmp1_outlen, KNET_DATABUFSIZE_CRYPT, (unsigned char *)buf_in, buf_in_len) != SECSuccess) { log_printf("PK11_CipherOp failed (encrypt) crypt_type=%d (err %d)", (int)cipher_to_nss[instance->crypto_cipher_type], PR_GetError()); goto out; } if (PK11_DigestFinal(crypt_context, data + tmp1_outlen, &tmp2_outlen, KNET_DATABUFSIZE_CRYPT - tmp1_outlen) != SECSuccess) { log_printf("PK11_DigestFinal failed (encrypt) crypt_type=%d (err %d)", (int)cipher_to_nss[instance->crypto_cipher_type], PR_GetError()); goto out; } *buf_out_len = tmp1_outlen + tmp2_outlen + SALT_SIZE; err = 0; out: if (crypt_context) { PK11_DestroyContext(crypt_context, PR_TRUE); } if (nss_sec_param) { SECITEM_FreeItem(nss_sec_param, PR_TRUE); } return err; } static int decrypt_nss ( struct crypto_instance *instance, unsigned char *buf, int *buf_len) { PK11Context* decrypt_context = NULL; SECItem decrypt_param; int tmp1_outlen = 0; unsigned int tmp2_outlen = 0; unsigned char *salt = buf; unsigned char *data = salt + SALT_SIZE; int datalen = *buf_len - SALT_SIZE; unsigned char outbuf[KNET_DATABUFSIZE_CRYPT]; int outbuf_len; int err = -1; /* Create cipher context for decryption */ decrypt_param.type = siBuffer; decrypt_param.data = salt; decrypt_param.len = SALT_SIZE; decrypt_context = PK11_CreateContextBySymKey(cipher_to_nss[instance->crypto_cipher_type], CKA_DECRYPT, instance->nss_sym_key, &decrypt_param); if (!decrypt_context) { log_printf("PK11_CreateContext (decrypt) failed (err %d)", PR_GetError()); goto out; } if (PK11_CipherOp(decrypt_context, outbuf, &tmp1_outlen, sizeof(outbuf), data, datalen) != SECSuccess) { log_printf("PK11_CipherOp (decrypt) failed (err %d)", PR_GetError()); goto out; } if (PK11_DigestFinal(decrypt_context, outbuf + tmp1_outlen, &tmp2_outlen, sizeof(outbuf) - tmp1_outlen) != SECSuccess) { log_printf("PK11_DigestFinal (decrypt) failed (err %d)", PR_GetError()); goto out; } outbuf_len = tmp1_outlen + tmp2_outlen; memset(buf, 0, *buf_len); memcpy(buf, outbuf, outbuf_len); *buf_len = outbuf_len; err = 0; out: if (decrypt_context) { PK11_DestroyContext(decrypt_context, PR_TRUE); } return err; } /* * hash/hmac/digest functions */ static int string_to_crypto_hash_type(const char* crypto_hash_type) { if (strcmp(crypto_hash_type, "none") == 0) { return CRYPTO_HASH_TYPE_NONE; } else if (strcmp(crypto_hash_type, "md5") == 0) { return CRYPTO_HASH_TYPE_MD5; } else if (strcmp(crypto_hash_type, "sha1") == 0) { return CRYPTO_HASH_TYPE_SHA1; } else if (strcmp(crypto_hash_type, "sha256") == 0) { return CRYPTO_HASH_TYPE_SHA256; } else if (strcmp(crypto_hash_type, "sha384") == 0) { return CRYPTO_HASH_TYPE_SHA384; } else if (strcmp(crypto_hash_type, "sha512") == 0) { return CRYPTO_HASH_TYPE_SHA512; } return -1; } static int init_nss_hash(struct crypto_instance *instance) { PK11SlotInfo* hash_slot = NULL; SECItem hash_param; if (!hash_to_nss[instance->crypto_hash_type]) { return 0; } hash_param.type = siBuffer; hash_param.data = 0; hash_param.len = 0; hash_slot = PK11_GetBestSlot(hash_to_nss[instance->crypto_hash_type], NULL); if (hash_slot == NULL) { log_printf("Unable to find security slot (err %d)", PR_GetError()); return -1; } instance->nss_sym_key_sign = PK11_ImportSymKey(hash_slot, hash_to_nss[instance->crypto_hash_type], PK11_OriginUnwrap, CKA_SIGN, &hash_param, NULL); if (instance->nss_sym_key_sign == NULL) { log_printf("Failure to import key into NSS (err %d)", PR_GetError()); return -1; } PK11_FreeSlot(hash_slot); return 0; } static int calculate_nss_hash( struct crypto_instance *instance, const unsigned char *buf, const size_t buf_len, unsigned char *hash) { PK11Context* hash_context = NULL; SECItem hash_param; unsigned int hash_tmp_outlen = 0; unsigned char hash_block[hash_block_len[instance->crypto_hash_type]]; int err = -1; /* Now do the digest */ hash_param.type = siBuffer; hash_param.data = 0; hash_param.len = 0; hash_context = PK11_CreateContextBySymKey(hash_to_nss[instance->crypto_hash_type], CKA_SIGN, instance->nss_sym_key_sign, &hash_param); if (!hash_context) { log_printf("PK11_CreateContext failed (hash) hash_type=%d (err %d)", (int)hash_to_nss[instance->crypto_hash_type], PR_GetError()); goto out; } if (PK11_DigestBegin(hash_context) != SECSuccess) { log_printf("PK11_DigestBegin failed (hash) hash_type=%d (err %d)", (int)hash_to_nss[instance->crypto_hash_type], PR_GetError()); goto out; } if (PK11_DigestOp(hash_context, buf, buf_len) != SECSuccess) { log_printf("PK11_DigestOp failed (hash) hash_type=%d (err %d)", (int)hash_to_nss[instance->crypto_hash_type], PR_GetError()); goto out; } if (PK11_DigestFinal(hash_context, hash_block, &hash_tmp_outlen, hash_block_len[instance->crypto_hash_type]) != SECSuccess) { log_printf("PK11_DigestFinale failed (hash) hash_type=%d (err %d)", (int)hash_to_nss[instance->crypto_hash_type], PR_GetError()); goto out; } memcpy(hash, hash_block, hash_len[instance->crypto_hash_type]); err = 0; out: if (hash_context) { PK11_DestroyContext(hash_context, PR_TRUE); } return err; } /* * global/glue nss functions */ static int init_nss_db(struct crypto_instance *instance) { if ((!cipher_to_nss[instance->crypto_cipher_type]) && (!hash_to_nss[instance->crypto_hash_type])) { return 0; } if (NSS_NoDB_Init(".") != SECSuccess) { log_printf("NSS DB initialization failed (err %d)", PR_GetError()); return -1; } return 0; } static int init_nss(struct crypto_instance *instance) { if (init_nss_db(instance) < 0) { return -1; } if (init_nss_crypto(instance) < 0) { return -1; } if (init_nss_hash(instance) < 0) { return -1; } return 0; } /* * exported API */ int crypto_encrypt_and_sign ( struct crypto_instance *instance, const unsigned char *buf_in, const size_t buf_in_len, unsigned char *buf_out, size_t *buf_out_len) { if (cipher_to_nss[instance->crypto_cipher_type]) { if (encrypt_nss(instance, buf_in, buf_in_len, buf_out, buf_out_len) < 0) { return -1; } } else { memcpy(buf_out, buf_in, buf_in_len); *buf_out_len = buf_in_len; } if (hash_to_nss[instance->crypto_hash_type]) { if (calculate_nss_hash(instance, buf_out, *buf_out_len, buf_out + *buf_out_len) < 0) { return -1; } *buf_out_len = *buf_out_len + hash_len[instance->crypto_hash_type]; } return 0; } int crypto_authenticate_and_decrypt (struct crypto_instance *instance, unsigned char *buf, int *buf_len) { if (hash_to_nss[instance->crypto_hash_type]) { unsigned char tmp_hash[hash_len[instance->crypto_hash_type]]; if (calculate_nss_hash(instance, buf, *buf_len - hash_len[instance->crypto_hash_type], tmp_hash) < 0) { return -1; } if (memcmp(tmp_hash, buf + (*buf_len - hash_len[instance->crypto_hash_type]), hash_len[instance->crypto_hash_type]) != 0) { log_printf("Digest does not match"); return -1; } *buf_len = *buf_len - hash_len[instance->crypto_hash_type]; } if (cipher_to_nss[instance->crypto_cipher_type]) { if (decrypt_nss(instance, buf, buf_len) < 0) { return -1; } } return 0; } int crypto_init( knet_handle_t knet_h, const struct knet_handle_cfg *knet_handle_cfg) { - log_printf("Initizializing crypto module\n"); + log_printf("Initizializing crypto module [%s/%s]", + knet_handle_cfg->crypto_cipher_type, + knet_handle_cfg->crypto_hash_type); knet_h->crypto_instance = malloc(sizeof(struct crypto_instance)); if (!knet_h->crypto_instance) { return -1; } memset(knet_h->crypto_instance, 0, sizeof(struct crypto_instance)); if (!knet_handle_cfg->crypto_cipher_type) { goto out_err; } knet_h->crypto_instance->crypto_cipher_type = string_to_crypto_cipher_type(knet_handle_cfg->crypto_cipher_type); if (knet_h->crypto_instance->crypto_cipher_type < 0) { goto out_err; } if (!knet_handle_cfg->crypto_hash_type) { goto out_err; } knet_h->crypto_instance->crypto_hash_type = string_to_crypto_hash_type(knet_handle_cfg->crypto_hash_type); if (knet_h->crypto_instance->crypto_hash_type < 0) { goto out_err; } knet_h->crypto_instance->private_key = knet_handle_cfg->private_key; knet_h->crypto_instance->private_key_len = knet_handle_cfg->private_key_len; if ((knet_h->crypto_instance->crypto_cipher_type > 0) || (knet_h->crypto_instance->crypto_hash_type > 0)) { if ((!knet_h->crypto_instance->private_key) || (knet_h->crypto_instance->private_key_len < 1024)) { goto out_err; } } knet_h->tap_to_links_buf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->tap_to_links_buf_crypt) goto out_err; knet_h->pingbuf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->pingbuf_crypt) goto out_err; knet_h->recv_from_links_buf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->recv_from_links_buf_crypt) goto out_err; knet_h->crypto_instance->private_key = knet_handle_cfg->private_key; knet_h->crypto_instance->private_key_len = knet_handle_cfg->private_key_len; if (init_nss(knet_h->crypto_instance) < 0) { goto out_err; } return 0; out_err: crypto_fini(knet_h); return -1; } void crypto_fini( knet_handle_t knet_h) { if (knet_h->crypto_instance) { if (knet_h->crypto_instance->nss_sym_key) PK11_FreeSymKey(knet_h->crypto_instance->nss_sym_key); if (knet_h->crypto_instance->nss_sym_key_sign) PK11_FreeSymKey(knet_h->crypto_instance->nss_sym_key_sign); if (knet_h->pingbuf_crypt) free(knet_h->pingbuf_crypt); if (knet_h->tap_to_links_buf_crypt) free(knet_h->tap_to_links_buf_crypt); if (knet_h->recv_from_links_buf_crypt) free(knet_h->recv_from_links_buf_crypt); free(knet_h->crypto_instance); knet_h->crypto_instance = NULL; } return; } diff --git a/tests/ping_test.c b/tests/ping_test.c index ad3a1dc5..8aee8b88 100644 --- a/tests/ping_test.c +++ b/tests/ping_test.c @@ -1,226 +1,254 @@ #include "config.h" #include #include #include #include #include #include #include #include "libknet.h" static unsigned char crypto_key[4096]; static int knet_sock[2]; static knet_handle_t knet_h; +static struct knet_handle_cfg knet_handle_cfg; static in_port_t tok_inport(char *str) { int value = atoi(str); if ((value < 0) || (value > UINT16_MAX)) return 0; return (in_port_t) value; } static int tok_inaddrport(char *str, struct sockaddr_in *addr) { char *strhost, *strport, *tmp = NULL; strhost = strtok_r(str, ":", &tmp); strport = strtok_r(NULL, ":", &tmp); if (strport == NULL) addr->sin_port = htons(KNET_RING_DEFPORT); else addr->sin_port = htons(tok_inport(strport)); return inet_aton(strhost, &addr->sin_addr); } static void print_usage(char *name) { printf("usage: %s [:] [:port] [...]\n", name); printf("example: %s 0.0.0.0 192.168.0.2\n", name); + printf("example: %s 127.0.0.1:50000 127.0.0.1:50000 crypto:aes256,sha1\n", name); +} + +static void set_crypto(int argc, char *argv[]) +{ + int i, found = 0; + + for (i = 0; i < argc; i++) { + if (!strncmp(argv[i], "crypto", 6)) { + printf("found crypto at: %i %s\n", i, argv[i]); + found = 1; + break; + } + } + if (!found) { + knet_handle_cfg.crypto_cipher_type = (char *)"none"; + knet_handle_cfg.crypto_hash_type = (char *)"none"; + } else { + char *tmp = NULL; + strtok_r(argv[i], ":", &tmp); + knet_handle_cfg.crypto_cipher_type = strtok_r(NULL, ",", &tmp); + knet_handle_cfg.crypto_hash_type = strtok_r(NULL, ",", &tmp); + } + printf("Setting up encryption: %s hmac: %s\n", + knet_handle_cfg.crypto_cipher_type, + knet_handle_cfg.crypto_hash_type); } static void argv_to_hosts(int argc, char *argv[]) { int err, i; struct sockaddr_in *address; struct knet_host *host; struct knet_listener *listener; listener = malloc(sizeof(struct knet_listener)); if (listener == NULL) { printf("Unable to create listener\n"); exit(EXIT_FAILURE); } memset(listener, 0, sizeof(struct knet_listener)); address = (struct sockaddr_in *) &listener->address; address->sin_family = AF_INET; err = tok_inaddrport(argv[1], address); if (err < 0) { printf("Unable to convert ip address: %s\n", argv[1]); exit(EXIT_FAILURE); } err = knet_listener_add(knet_h, listener); if (err != 0) { printf("Unable to start knet listener\n"); exit(EXIT_FAILURE); } for (i = 2; i < argc; i++) { + if (!strncmp(argv[i], "crypto", 6)) + continue; + if (knet_host_add(knet_h, i - 1) != 0) { printf("Unable to add new knet_host\n"); exit(EXIT_FAILURE); } knet_host_get(knet_h, i - 1, &host); host->link[0].sock = listener->sock; host->link[0].address.ss_family = AF_INET; knet_link_timeout(&host->link[0], 1000, 5000, 2048); host->link[0].ready = 1; err = tok_inaddrport(argv[i], (struct sockaddr_in *) &host->link[0].address); if (err < 0) { printf("Unable to convert ip address: %s", argv[i]); exit(EXIT_FAILURE); } knet_host_release(knet_h, &host); } } /* Testing the latency/timeout: * # tc qdisc add dev lo root handle 1:0 netem delay 1s limit 1000 * # tc -d qdisc show dev lo * # tc qdisc del dev lo root */ static int print_link(knet_handle_t khandle, struct knet_host *host, struct knet_host_search *data) { int i; for (i = 0; i < KNET_MAX_LINK; i++) { if (host->link[i].ready != 1) continue; printf("host %p, link %p latency is %llu microsecs, status: %s\n", host, &host->link[i], host->link[i].latency, (host->link[i].enabled == 0) ? "disabled" : "enabled"); } return KNET_HOST_FOREACH_NEXT; } static void sigint_handler(int signum) { int err; printf("Cleaning up...\n"); if (knet_h != NULL) { err = knet_handle_free(knet_h); if (err != 0) { printf("Unable to cleanup before exit\n"); exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); } int main(int argc, char *argv[]) { char buff[1024]; size_t len; fd_set rfds; struct timeval tv; struct knet_host_search print_search; - struct knet_handle_cfg knet_handle_cfg; if (argc < 3) { print_usage(argv[0]); exit(EXIT_FAILURE); } if (socketpair(AF_UNIX, SOCK_STREAM, IPPROTO_IP, knet_sock) != 0) { printf("Unable to create socket\n"); exit(EXIT_FAILURE); } knet_h = NULL; if (signal(SIGINT, sigint_handler) == SIG_ERR) { printf("Unable to configure SIGINT handler\n"); exit(EXIT_FAILURE); } memset(&knet_handle_cfg, 0, sizeof(struct knet_handle_cfg)); knet_handle_cfg.fd = knet_sock[0]; knet_handle_cfg.node_id = 1; - knet_handle_cfg.crypto_cipher_type = (char *)"aes256"; - knet_handle_cfg.crypto_hash_type = (char *)"sha1"; + set_crypto(argc, argv); knet_handle_cfg.private_key = crypto_key; knet_handle_cfg.private_key_len = sizeof(crypto_key); /* * fake a 4KB key of all 0's */ memset(knet_handle_cfg.private_key, 0, sizeof(crypto_key)); if ((knet_h = knet_handle_new(&knet_handle_cfg)) == NULL) { printf("Unable to create new knet_handle_t\n"); exit(EXIT_FAILURE); } argv_to_hosts(argc, argv); knet_handle_setfwd(knet_h, 1); while (1) { knet_host_foreach(knet_h, print_link, &print_search); printf("Sending 'Hello World!' frame\n"); write(knet_sock[1], "Hello World!", 13); tv.tv_sec = 5; tv.tv_usec = 0; select_loop: FD_ZERO(&rfds); FD_SET(knet_sock[1], &rfds); len = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); /* uncomment this to replicate the one-message problem */ /* usleep(500000); */ if (len < 0) { printf("Unable select over knet_handle_t\n"); exit(EXIT_FAILURE); } else if (FD_ISSET(knet_sock[1], &rfds)) { len = read(knet_sock[1], buff, sizeof(buff)); printf("Received data (%zu bytes): '%s'\n", len, buff); } if ((tv.tv_sec > 0) || (tv.tv_usec > 0)) goto select_loop; } /* FIXME: allocated hosts should be free'd */ return 0; }