summaryrefslogtreecommitdiffstats
path: root/network_cmds/mptcp_client/mptcp_client.c
diff options
context:
space:
mode:
authorCameron Katri <me@cameronkatri.com>2021-05-09 14:20:58 -0400
committerCameron Katri <me@cameronkatri.com>2021-05-09 14:20:58 -0400
commit5fd83771641d15c418f747bd343ba6738d3875f7 (patch)
tree5abf0f78f680d9837dbd93d4d4c3933bb7509599 /network_cmds/mptcp_client/mptcp_client.c
downloadapple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.gz
apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.zst
apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.zip
Import macOS userland
adv_cmds-176 basic_cmds-55 bootstrap_cmds-116.100.1 developer_cmds-66 diskdev_cmds-667.40.1 doc_cmds-53.60.1 file_cmds-321.40.3 mail_cmds-35 misc_cmds-34 network_cmds-606.40.1 patch_cmds-17 remote_cmds-63 shell_cmds-216.60.1 system_cmds-880.60.2 text_cmds-106
Diffstat (limited to 'network_cmds/mptcp_client/mptcp_client.c')
-rw-r--r--network_cmds/mptcp_client/mptcp_client.c701
1 files changed, 701 insertions, 0 deletions
diff --git a/network_cmds/mptcp_client/mptcp_client.c b/network_cmds/mptcp_client/mptcp_client.c
new file mode 100644
index 0000000..770aabc
--- /dev/null
+++ b/network_cmds/mptcp_client/mptcp_client.c
@@ -0,0 +1,701 @@
+/*
+ * 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;
+
+ buf = malloc(bufsz);
+ if (!buf)
+ return NULL;
+
+ bzero(buf, bufsz);
+ strlcpy(buf, MSG_HDR, bufsz);
+
+ 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;
+
+ buf = malloc(bufsz);
+ if (!buf)
+ return NULL;
+
+ bzero(buf, bufsz);
+ strlcpy(buf, MSG_HDR, bufsz);
+
+ 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;
+
+ buf = malloc(bufsz);
+ if (!buf)
+ return NULL;
+
+ 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 },
+ { "--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: ");
+}
+
+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' },
+ { "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, portno;
+ ssize_t n;
+ int reqlen = 256;
+ int rsplen = 256;
+ int ntimes = 1;
+ 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;
+ sae_connid_t cid1, cid2;
+ int iter;
+ int bytes_to_rdwr;
+ 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";
+ int gotopt = 0;
+
+ thiszone = gmt2local(0);
+
+ while ((ch = getopt_long(argc, argv, "a:c:hn:p:qr:R:v", longopts, NULL)) != -1) {
+ gotopt = 1;
+ switch (ch) {
+ case 'a':
+ alt_addr_arg = optarg;
+ break;
+ case 'c':
+ host_arg = optarg;
+ break;
+ case 'n':
+ ntimes_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;
+ 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];
+ } 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 || reqlen > 1024 * 1024)
+ errx(EX_USAGE, "invalid request length %s\n", reqlen_arg);
+ }
+
+ if (rsplen_arg != NULL) {
+ rsplen = atoi(rsplen_arg);
+ if (rsplen < 0 || rsplen > 1024 * 1024)
+ 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);
+ }
+
+ 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\n",
+ host_arg, port_arg, reqlen, rsplen, ntimes, alt_addr_arg);
+
+ sockfd = socket(AF_MULTIPATH, SOCK_STREAM, 0);
+ if (sockfd < 0)
+ err(EX_OSERR, "ERROR opening socket");
+
+ 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;
+
+ cid1 = cid2 = SAE_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);
+ }
+ sa_endpoints_t sa;
+ bzero(&sa, sizeof(sa));
+ sa.sae_dstaddr = ares->ai_addr;
+ sa.sae_dstaddrlen = ares->ai_addrlen;
+ sa.sae_srcif = ifscope;
+
+ error = connectx(sockfd, &sa, SAE_ASSOCID_ANY, 0, NULL, 0, NULL, &cid1);
+ if (error != 0)
+ err(EX_OSERR, "ERROR connecting");
+
+ iter = 0;
+
+ while (ntimes) {
+ if (iter == 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);
+ }
+ sa_endpoints_t sa;
+ bzero(&sa, sizeof(sa));
+ sa.sae_srcif = ifscope;
+ sa.sae_srcaddr = altres->ai_addr;
+ sa.sae_srcaddrlen = altres->ai_addrlen;
+ sa.sae_dstaddr = ares->ai_addr;
+ sa.sae_dstaddrlen = ares->ai_addrlen;
+
+ error = connectx(sockfd, &sa, SAE_ASSOCID_ANY, 0, NULL, 0, NULL, &cid2);
+ if (error < 0) {
+ err(EX_OSERR, "ERROR setting up alternate path");
+ }
+ }
+ }
+ }
+
+ 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 (verbose) {
+ ts_print();
+ printf("close(%d)\n", sockfd);
+ }
+ close(sockfd);
+
+ 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, sae_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");
+
+ 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;
+ }
+ }
+out:
+ if (cfo != NULL)
+ freeconninfo(cfo);
+
+ return (err);
+}
+
+static void
+showmpinfo(int s)
+{
+ uint32_t aid_cnt = 0, cid_cnt = 0;
+ sae_associd_t *aid = NULL;
+ sae_connid_t *cid = NULL;
+ int i, error = 0;
+
+ 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, SAE_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);
+}