mptcp_client.c   [plain text]


/*
 * Copyright (c) 2012-2014 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/*
 * Copyright (c) 1997
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by the University of California,
 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
 * the University nor the names of its contributors may be used to endorse
 * or promote products derived from this software without specific prior
 * written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

//
//  Created by Anumita Biswas on 7/17/12.
//

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <arpa/inet.h>
#include <err.h>
#include <sysexits.h>
#include <getopt.h>

#include "conn_lib.h"

struct so_cordreq socorder;
static void showmpinfo(int s);

#define MSG_HDR "Message Header"
#define RESPONSE "I got your message"

static int verbose = 0;

static int32_t thiszone = 0;	/* time difference with gmt */

char* setup_buffer1(int bufsz)
{
	int i = 0, j = 1;
        char *buf = malloc(bufsz);
        if (buf) {
                bzero(buf, bufsz);
                strlcpy(buf, MSG_HDR, sizeof(MSG_HDR));
        }
	for (i = sizeof(MSG_HDR); i < bufsz; i++) {
		buf[i] = j;
		j++;
		if (j >= 255)
			j = 1;
	}
        return buf;
}

char* setup_buffer2(int bufsz)
{
	int i = 0;
	char j = 'A';
        char *buf = malloc(bufsz);
        if (buf) {
                bzero(buf, bufsz);
                strlcpy(buf, MSG_HDR, sizeof(MSG_HDR));
        }
	for (i = sizeof(MSG_HDR); i < bufsz; i++) {
		buf[i] = j;
		j++;
		if (j >= 'z')
			j = 'A';
	}
        return buf;
}

char *setup_buffer3(int bufsz)
{
	char *buf = malloc(bufsz);
	if (buf) {
		bzero(buf, bufsz);
	}
	return buf;
}

/*
 * Returns the difference between gmt and local time in seconds.
 * Use gmtime() and localtime() to keep things simple.
 * from tcpdump/gmt2local.c
 */
static int32_t
gmt2local(time_t t)
{
	int dt, dir;
	struct tm *gmt, *loc;
	struct tm sgmt;
	
	if (t == 0)
		t = time(NULL);
	gmt = &sgmt;
	*gmt = *gmtime(&t);
	loc = localtime(&t);
	dt = (loc->tm_hour - gmt->tm_hour) * 60 * 60 +
	(loc->tm_min - gmt->tm_min) * 60;
	
	/*
	 * If the year or julian day is different, we span 00:00 GMT
	 * and must add or subtract a day. Check the year first to
	 * avoid problems when the julian day wraps.
	 */
	dir = loc->tm_year - gmt->tm_year;
	if (dir == 0)
		dir = loc->tm_yday - gmt->tm_yday;
	dt += dir * 24 * 60 * 60;
	
	return (dt);
}

/*
 * Print the timestamp
 * from tcpdump/util.c
 */
static void
ts_print(void)
{
	int s;
	struct timeval tv;
	
	gettimeofday(&tv, NULL);
	
	/* Default */
	s = (tv.tv_sec + thiszone) % 86400;
	printf("%02d:%02d:%02d.%06u ", s / 3600, (s % 3600) / 60, s % 60,
	       (u_int32_t)tv.tv_usec);
}

static const char *
basename(const char * str)
{
	const char *last_slash = strrchr(str, '/');
	
	if (last_slash == NULL)
		return (str);
	else
		return (last_slash + 1);
}

struct option_desc {
	const char *option;
	const char *description;
	int required;
};

struct option_desc option_desc_list[] = {
	{ "--host addr", "address of server to connect to", 1 },
	{ "--port n", "port of server to connect to", 1 },
	{ "--reqlen n", "length of request (256 by default)", 0 },
	{ "--rsplen n", "length of response (256 by default)", 0 },
	{ "--ntimes n", "number of time to send request (1 by default)", 0 },
	{ "--alt_addr addr", "alternate server to connect to", 0 },
	{ "--connorder add", "alternate server to connect to", 0 },
	{ "--longlived n", "number of reconnection for long lived (default 0)", 0 },
	{ "--fastjoin (0|1)", "use fast join (default 0)", 0 },
	{ "--nowaitforjoin (0|1)", "do not wait for join (default 0 -- i.e. wait)", 0 },
	{ "--verbose", "increase verbosity", 0 },
	{ "--help", "display this help", 0 },

	{ NULL, NULL, 0 }  /* Mark end of list */
};

static void
usage(const char *cmd)
{
	struct option_desc *option_desc;
	char *usage_str = malloc(LINE_MAX);
	size_t usage_len;
	
	if (usage_str == NULL)
		err(1, "%s: malloc(%d)", __func__, LINE_MAX);
	
	usage_len = snprintf(usage_str, LINE_MAX, "# usage: %s ", basename(cmd));
	
	for (option_desc = option_desc_list; option_desc->option != NULL; option_desc++) {
		int len;
		
		if (option_desc->required)
			len = snprintf(usage_str + usage_len, LINE_MAX - usage_len, "%s ", option_desc->option);
		else
			len = snprintf(usage_str + usage_len, LINE_MAX - usage_len, "[%s] ", option_desc->option);
		if (len < 0)
			err(1, "%s: snprintf(", __func__);
		
		usage_len += len;
		if (usage_len > LINE_MAX)
			break;
	}
	printf("%s\n", usage_str);
	printf("options:\n");
	
	for (option_desc = option_desc_list; option_desc->option != NULL; option_desc++) {
		printf(" %-24s # %s\n", option_desc->option, option_desc->description);
	}
	printf("\n");
	printf("# legacy usage: ");
	printf("%s hostname port reqlen rsplen ntimes alt_addr 0 connorder longlived fastjoin nowaitforjoin\n",
		basename(cmd));
}

static struct option longopts[] = {
	{ "host",		required_argument, 	NULL, 		'c' },
	{ "port",		required_argument, 	NULL, 		'p' },
	{ "reqlen",		required_argument, 	NULL, 		'r' },
	{ "rsplen",		required_argument, 	NULL,		'R' },
	{ "ntimes",		required_argument, 	NULL, 		'n' },
	{ "alt_addr",		required_argument, 	NULL,		'a' },
	{ "connorder",		required_argument, 	NULL,		'o' },
	{ "longlived",		required_argument, 	NULL, 		'l' },
	{ "fastjoin",		required_argument, 	NULL,		'f' },
	{ "nowaitforjoin",	required_argument, 	NULL, 		'w' },
	{ "help",		no_argument, 		NULL, 		'h' },
	{ "verbose",		no_argument, 		NULL, 		'v' },
	{ "quiet",		no_argument, 		NULL, 		'q' },
	{ NULL,			0, 			NULL,		0 }
	
};

static int
sprint_sockaddr(char *str, socklen_t strlen, struct sockaddr *sa)
{
	int retval = 0;

	if (sa->sa_family == AF_INET) {
		struct sockaddr_in      *sin = (struct sockaddr_in*)sa;
		char str4[INET_ADDRSTRLEN];
		
		inet_ntop(AF_INET, &sin->sin_addr, str4, sizeof(str4));
		
		retval = snprintf(str, strlen, "%s:%u", str4, ntohs(sin->sin_port));
	} else  if (sa->sa_family == AF_INET6) {
		struct sockaddr_in6     *sin6 = (struct sockaddr_in6*)sa;
		char                    str6[INET6_ADDRSTRLEN];
		char                    ifname[IF_NAMESIZE];
		char                    scopestr[2 + IF_NAMESIZE];
		
		inet_ntop(AF_INET6, &sin6->sin6_addr, str6, sizeof(str6));

		if (sin6->sin6_scope_id == 0)
			*scopestr = '\0';
		else {
			if_indextoname(sin6->sin6_scope_id, ifname);
			snprintf(scopestr, sizeof(scopestr), "%%%s", ifname);
		}
		
		retval = snprintf(str, strlen, "%s%s:%u",
			str6,
			scopestr,
			ntohs(sin6->sin6_port));
	}
	return (retval);
}

int main(int argc, char * const *argv)
{
	int sockfd, ps, portno;
	ssize_t n;
	int reqlen = 256;
	int rsplen = 256;
	int ntimes = 1;
	int connordrtest = 0;
	int longlived = 0;
	int fastjoin = 0;
	char *buffer = NULL;
	char *buffer1;
	char *buffer2;
	char *buffer3;
	struct addrinfo *ares = NULL, ahints;
	struct addrinfo *altres = NULL;
	int retval = 0;
	int which_buf = 0;
	connid_t cid1, cid2;
	int iter;
	int bytes_to_rdwr;
	int peeled_off = 0;
	int nowaitforjoin = 0;
	int ch;
	const char *host_arg = NULL;
	const char *port_arg = NULL;
	const char *reqlen_arg = "256";
	const char *rsplen_arg = "256";
	const char *ntimes_arg = "1";
	const char *alt_addr_arg = NULL;
	const char *alt_port_arg = "0";
	const char *connorder_arg = NULL;
	const char *longlived_arg = NULL;
	const char *fastjoin_arg = NULL;
	const char *nowaitforjoin_arg = NULL;
	int gotopt = 0;

	thiszone = gmt2local(0);
	
	while ((ch = getopt_long(argc, argv, "a:c:f:hl:n:o:p:qr:R:vw:", longopts, NULL)) != -1) {
		gotopt = 1;
		switch (ch) {
			case 'a':
				alt_addr_arg = optarg;
				break;
			case 'c':
				host_arg = optarg;
				break;
			case 'f':
				fastjoin_arg = optarg;
				break;
			case 'l':
				longlived_arg = optarg;
				break;
			case 'n':
				ntimes_arg = optarg;
				break;
			case 'o':
				connorder_arg = optarg;
				break;
			case 'p':
				port_arg = optarg;
				break;
			case 'q':
				verbose--;
				break;
			case 'r':
				reqlen_arg = optarg;
				break;
			case 'R':
				rsplen_arg = optarg;
				break;
			case 'v':
				verbose++;
				break;
			case 'w':
				nowaitforjoin_arg = optarg;
				break;
				
			default:
				usage(argv[0]);
				exit(EX_USAGE);
			
		}
	}
	
	if (gotopt == 0) {
		if (argc == 12) {
			host_arg = argv[1];
			port_arg = argv[2];
			reqlen_arg = argv[3];
			rsplen_arg = argv[4];
			ntimes_arg = argv[5];
			alt_addr_arg = argv[6];
			connorder_arg = argv[8];
			longlived_arg = argv[9];
			fastjoin_arg = argv[10];
			nowaitforjoin_arg = argv[11];
		} else {
			usage(argv[0]);
			exit(EX_USAGE);
		}
	}
	
	if (host_arg == NULL)
		errx(EX_USAGE, "missing required host option\n");

	if (port_arg == NULL)
		errx(EX_USAGE, "missing required port option\n");
	portno = atoi(port_arg);
	if (portno < 0 || portno > 65535)
		errx(EX_USAGE, "invalid port %s\n", port_arg);

	if (reqlen_arg != NULL) {
		reqlen = atoi(reqlen_arg);
		if (reqlen < 0)
			errx(EX_USAGE, "invalid request length %s\n", reqlen_arg);
	}

	if (rsplen_arg != NULL) {
		rsplen = atoi(rsplen_arg);
		if (rsplen < 0)
			errx(EX_USAGE, "invalid response length %s\n", rsplen_arg);
	}
	
	if (ntimes_arg != NULL) {
		ntimes = atoi(ntimes_arg);
		if (ntimes < 1)
			errx(EX_USAGE, "invalid ntimes option %s\n", ntimes_arg);
	}
	
	if (connorder_arg != NULL) {
		connordrtest = atoi(connorder_arg);
		if (connordrtest != 0 && connordrtest != 1)
			errx(EX_USAGE, "invalid connorder count %s\n", connorder_arg);
	}
	
	if (longlived_arg != NULL) {
		longlived = atoi(longlived_arg);
		if (longlived < 0)
			errx(EX_USAGE, "invalid longlived count %s\n", longlived_arg);
	}
	
	if (fastjoin_arg != NULL) {
		fastjoin = atoi(fastjoin_arg);
		if (fastjoin != 0 && fastjoin != 1)
			errx(EX_USAGE, "invalid fastjoin option %s\n", fastjoin_arg);
	}
	
	if (nowaitforjoin_arg != NULL) {
		nowaitforjoin = atoi(nowaitforjoin_arg);
		if (nowaitforjoin != 0 && nowaitforjoin != 1)
			errx(EX_USAGE, "invalid nowaitforjoin option %s\n", nowaitforjoin_arg);
	}
	
	buffer1 = setup_buffer1(reqlen);
	if (!buffer1) {
		printf("client: failed to alloc buffer space \n");
		return -1;
	}
	
	buffer2 = setup_buffer2(reqlen);
	if (!buffer2) {
		printf("client: failed to alloc buffer space \n");
		return -1;
	}
	
	buffer3 = setup_buffer3(rsplen);
	if (!buffer3) {
		printf("client: failed to alloc buffer space \n");
		return -1;
	}
	
	if (verbose > 0)
		printf("host: %s port: %s reqlen: %d rsplen: %d ntimes: %d alt_addr: %s connorder: %d longlived: %d fasjoin: %d nowaitforjoin: %d\n",
			host_arg, port_arg, reqlen, rsplen, ntimes, alt_addr_arg, connordrtest, longlived, fastjoin, nowaitforjoin);
	
	sockfd = socket(AF_MULTIPATH, SOCK_STREAM, 0);
	if (sockfd < 0)
		err(EX_OSERR, "ERROR opening socket");
#define SO_MPTCP_FASTJOIN 0x1111
	opterr = setsockopt(sockfd, SOL_SOCKET, SO_MPTCP_FASTJOIN, &fastjoin, sizeof(fastjoin));
	if (opterr != 0)
		warn("setsockopt(SO_MPTCP_FASTJOIN, %d)", fastjoin);
	
	memset(&ahints, 0, sizeof(struct addrinfo));
	ahints.ai_family = AF_INET;
	ahints.ai_socktype = SOCK_STREAM;
	ahints.ai_protocol = IPPROTO_TCP;
	
	retval = getaddrinfo(host_arg, port_arg, &ahints, &ares);
	if (retval != 0)
		printf("getaddrinfo(%s, %s) failed %d\n", host_arg, port_arg, retval);
	
	bytes_to_rdwr = reqlen;
connect_again:
	
	cid1 = cid2 = CONNID_ANY;
	int ifscope = 0;
	int error = 0;
	
	if (verbose > 0) {
		char str[2 * INET6_ADDRSTRLEN];
		
		ts_print();
		
		sprint_sockaddr(str, sizeof(str), ares->ai_addr);
		printf("connectx(%s, %d, %d)\n", str, ifscope, cid1);
	}
	
	error = connectx(sockfd, NULL, 0, ares->ai_addr,
		       ares->ai_addrlen, ifscope, ASSOCID_ANY, &cid1);
	if ((error != 0) && (errno != EPROTO)) {
		err(EX_OSERR, "ERROR connecting");
	} else if ((error != 0) && (errno == EPROTO)) {
		ps = peeloff(sockfd, ASSOCID_ANY);
		
		if (ps != -1) {
			close(sockfd);
			sockfd = ps;
		}
		peeled_off = 1;
		ts_print();
		printf("%s: peeled off\n", __func__);
	}
	
	
	iter = 0;
	
	while (ntimes) {
		
		if ((iter == 0) && (peeled_off == 0)) {
			/* Add alternate path if available */
			
			if (alt_addr_arg && alt_addr_arg[0] != 0) {
				retval = getaddrinfo(alt_addr_arg, alt_port_arg, &ahints, &altres);
				
				if (retval != 0)
					printf("client: alternate address resolution failed. \n");
				else {
					printf("client: connecting to alternate address (ifscope %d)\n", ifscope);

					if (verbose > 0) {
						char str[2 * INET6_ADDRSTRLEN];

						ts_print();
						
						sprint_sockaddr(str, sizeof(str), altres->ai_addr);
						printf("connectx(%s, %d, %d)\n", str, ifscope, cid1);
					}
					
					error = connectx(sockfd, altres->ai_addr, altres->ai_addrlen,
						       ares->ai_addr, ares->ai_addrlen,
						       ifscope, ASSOCID_ANY, &cid2);
					if (error < 0) {
						err(EX_OSERR, "ERROR setting up alternate path");
					}
				}
			}
			
		}
		
		if ((iter == 10) && (connordrtest == 1)) {
			int retval = 0;

			socorder.sco_cid = cid2;
			socorder.sco_rank = 1;
			retval = ioctl(sockfd, SIOCSCONNORDER, &socorder);
			if (retval != 0)
				warn("Error in changing priority");

			bzero(&socorder, sizeof(socorder));
			socorder.sco_cid = cid2;
			retval = ioctl(sockfd, SIOCGCONNORDER, &socorder);
			printf("cid %d rank %d", socorder.sco_cid, socorder.sco_rank);

			socorder.sco_cid = cid1;
			socorder.sco_rank = 0;
			retval = ioctl(sockfd, SIOCSCONNORDER, &socorder);
			if (retval != 0)
				warn("Error in changing priority");

			bzero(&socorder, sizeof(socorder));
			socorder.sco_cid = cid1;
			retval = ioctl(sockfd, SIOCGCONNORDER, &socorder);
			printf("cid %d rank %d \n", socorder.sco_cid, socorder.sco_rank);
		}
		
		if (which_buf == 0) {
			buffer = buffer1;
			which_buf = 1;
		} else {
			buffer = buffer2;
			which_buf = 0;
		}
		
		while (bytes_to_rdwr) {
			if (verbose) {
				ts_print();
				printf("writing %d bytes\n", bytes_to_rdwr);
			}
			n = write(sockfd, buffer, bytes_to_rdwr);
			if (n <= 0) {
				err(EX_OSERR, "ERROR writing to socket");
			}
			if (n <= bytes_to_rdwr)
				bytes_to_rdwr -= n;
			else {
				errx(EX_DATAERR, "ERROR extra data write %zd %d\n", n, bytes_to_rdwr);
			}
		}
		bytes_to_rdwr = rsplen;
		while (bytes_to_rdwr) {
			if (verbose) {
				ts_print();
				printf("reading %d bytes\n", rsplen);
			}
			n = read(sockfd, buffer3, rsplen);
			
			if (n <= 0) {
				err(EX_OSERR, "ERROR reading from socket");
			}
			if (n <= bytes_to_rdwr)
				bytes_to_rdwr -= n;
			else {
				errx(EX_DATAERR, "ERROR extra bytes read n:%zd expected:%d\n", n, bytes_to_rdwr);
			}
		}
		bytes_to_rdwr = reqlen;
		ntimes--;
		iter++;
	}
	
	printf("client: Req size %d Rsp size %d Read/Write %d times \n", reqlen, rsplen, iter);
	
	showmpinfo(sockfd);
	
	if ((!longlived) || (peeled_off == 1)) {
		if (verbose) {
			ts_print();
			printf("close(%d)\n", sockfd);
		}
		close(sockfd);
	} else {
		printf("Longlived countdown # %d. \n", longlived);
		if (verbose) {
			ts_print();
			printf("disconnectx(%d, %d)\n", sockfd, cid1);
		}
		disconnectx(sockfd, ASSOCID_ANY, cid1);
		if (cid2 != CONNID_ANY) {
			if (verbose) {
				ts_print();
				printf("disconnectx(%d, %d)\n", sockfd, cid2);
			}
			disconnectx(sockfd, ASSOCID_ANY, cid2);
		}
		if (!nowaitforjoin) {
			if (verbose) {
				ts_print();
				printf("sleep(10)\n");
			}
			sleep(10);
		}
		longlived--;
		
		ntimes = atoi(ntimes_arg);
		
		/* If fastjoin must be tested, write some data before doing the next connectx() */
		bytes_to_rdwr = reqlen / 2;
		if (verbose) {
			ts_print();
			printf("fastjoin writing %d bytes\n", bytes_to_rdwr);
		}
		n = write(sockfd, buffer, bytes_to_rdwr);
		if (n <= 0) {
			warnx("Fastjoin: Error writing to socket. \n");
		} else {
			bytes_to_rdwr = reqlen - (int)n;
			printf("FastJoin: Wrote %zd bytes, remaining %d of %d \n", n, bytes_to_rdwr, reqlen);
		}
		
		goto connect_again;
	}
	if (ares)
		freeaddrinfo(ares);
	if (altres)
		freeaddrinfo(altres);
	return 0;
}

#define	CIF_BITS	\
"\020\1CONNECTING\2CONNECTED\3DISCONNECTING\4DISCONNECTED\5BOUND_IF"\
"\6BOUND_IP\7BOUND_PORT\10PREFERRED\11MP_CAPABLE\12MP_READY" \
"\13MP_DEGRADED"

/*
 * Print a value a la the %b format of the kernel's printf
 */
static void
printb(const char *s, unsigned v, const char *bits)
{
	int i, any = 0;
	char c;
	
	if (bits && *bits == 8)
		printf("%s=%o", s, v);
	else
		printf("%s=%x", s, v);
	bits++;
	if (bits) {
		putchar('<');
		while ((i = *bits++) != '\0') {
			if (v & (1 << (i-1))) {
				if (any)
					putchar(',');
				any = 1;
				for (; (c = *bits) > 32; bits++)
					putchar(c);
			} else {
				for (; *bits > 32; bits++)
					;
			}
		}
		putchar('>');
	}
}

static int
showconninfo(int s, connid_t cid)
{
	char buf[INET6_ADDRSTRLEN];
	conninfo_t *cfo = NULL;
	int err;
	
	err = copyconninfo(s, cid, &cfo);
	if (err != 0) {
		printf("getconninfo failed for cid %d\n", cid);
		goto out;
	}
	
	printf("%6d:\t", cid);
	printb("flags", cfo->ci_flags, CIF_BITS);
	printf("\n");
	//printf("\toutif %s\n", if_indextoname(cfo->ci_ifindex, buf));
#if 1
	if (cfo->ci_src != NULL) {
		printf("\tsrc %s port %d\n", inet_ntop(cfo->ci_src->sa_family,
						       (cfo->ci_src->sa_family == AF_INET) ?
						       (void *)&((struct sockaddr_in *)cfo->ci_src)->
						       sin_addr.s_addr :
						       (void *)&((struct sockaddr_in6 *)cfo->ci_src)->sin6_addr,
						       buf, sizeof (buf)),
		       (cfo->ci_src->sa_family == AF_INET) ?
		       ntohs(((struct sockaddr_in *)cfo->ci_src)->sin_port) :
		       ntohs(((struct sockaddr_in6 *)cfo->ci_src)->sin6_port));
	}
	if (cfo->ci_dst != NULL) {
		printf("\tdst %s port %d\n", inet_ntop(cfo->ci_dst->sa_family,
						       (cfo->ci_dst->sa_family == AF_INET) ?
						       (void *)&((struct sockaddr_in *)cfo->ci_dst)->
						       sin_addr.s_addr :
						       (void *)&((struct sockaddr_in6 *)cfo->ci_dst)->sin6_addr,
						       buf, sizeof (buf)),
		       (cfo->ci_dst->sa_family == AF_INET) ?
		       ntohs(((struct sockaddr_in *)cfo->ci_dst)->sin_port) :
		       ntohs(((struct sockaddr_in6 *)cfo->ci_dst)->sin6_port));
	}
	if (cfo->ci_aux_data != NULL) {
		switch (cfo->ci_aux_type) {
			case CIAUX_TCP:
				printf("\tTCP aux info available\n");
				break;
			default:
				printf("\tUnknown aux type %d\n", cfo->ci_aux_type);
				break;
		}
	}
#endif
out:
	if (cfo != NULL)
		freeconninfo(cfo);
	
	return (err);
}

static void
showmpinfo(int s)
{
	uint32_t aid_cnt, cid_cnt;
	associd_t *aid = NULL;
	connid_t *cid = NULL;
	int i, error;
	
	error = copyassocids(s, &aid, &aid_cnt);
	if (error != 0) {
		printf("copyassocids failed\n");
		goto done;
	} else {
		printf("found %d associations", aid_cnt);
		if (aid_cnt > 0) {
			printf(" with IDs:");
			for (i = 0; i < aid_cnt; i++)
				printf(" %d\n", aid[i]);
		}
		printf("\n");
	}
	
	/* just do an association for now */
	error = copyconnids(s, ASSOCID_ANY, &cid, &cid_cnt);
	if (error != 0) {
		warn("getconnids failed\n");
		goto done;
	} else {
		printf("found %d connections", cid_cnt);
		if (cid_cnt > 0) {
			printf(":\n");
			for (i = 0; i < cid_cnt; i++) {
				if (showconninfo(s, cid[i]) != 0)
					break;
			}
		}
		printf("\n");
	}
	
done:
	if (aid != NULL)
		freeassocids(aid);
	if (cid != NULL)
		freeconnids(cid);
}