Exemple #1
0
static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint16_t len) {
	switch(type) {
		case SPTPS_HANDSHAKE:
			return sptps_send_record(&sptps, 0, cookie, sizeof cookie);

		case 0:
			data = xrealloc(data, datalen + len + 1);
			memcpy(data + datalen, msg, len);
			datalen += len;
			data[datalen] = 0;
			break;

		case 1:
			return finalize_join();

		case 2:
			fprintf(stderr, "Invitation succesfully accepted.\n");
			shutdown(sock, SHUT_RDWR);
			success = true;
			break;

		default:
			return false;
	}

	return true;
}
Exemple #2
0
static bool finalize_invitation(connection_t *c, const char *data, uint16_t len) {
	if(strchr(data, '\n')) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Received invalid key from invited node %s (%s)!\n", c->name, c->hostname);
		return false;
	}

	// Create a new host config file
	char filename[PATH_MAX];
	snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", confbase, c->name);
	if(!access(filename, F_OK)) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Host config file for %s (%s) already exists!\n", c->name, c->hostname);
		return false;
	}

	FILE *f = fopen(filename, "w");
	if(!f) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to create %s: %s\n", filename, strerror(errno));
		return false;
	}

	fprintf(f, "Ed25519PublicKey = %s\n", data);
	fclose(f);

	logger(DEBUG_CONNECTIONS, LOG_INFO, "Key succesfully received from %s (%s)", c->name, c->hostname);

	// Call invitation-accepted script
	char *envp[7] = {NULL};
	char *address, *port;

	xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
        xasprintf(&envp[1], "DEVICE=%s", device ? : "");
        xasprintf(&envp[2], "INTERFACE=%s", iface ? : "");
        xasprintf(&envp[3], "NODE=%s", c->name);
	sockaddr2str(&c->address, &address, &port);
	xasprintf(&envp[4], "REMOTEADDRESS=%s", address);
	xasprintf(&envp[5], "NAME=%s", myself->name);

	execute_script("invitation-accepted", envp);

	for(int i = 0; envp[i] && i < 7; i++)
		free(envp[i]);

	sptps_send_record(&c->sptps, 2, data, 0);
	return true;
}
Exemple #3
0
static bool receive_invitation_sptps(void *handle, uint8_t type, const void *data, uint16_t len) {
	connection_t *c = handle;

	if(type == 128)
		return true;

	if(type == 1 && c->status.invitation_used)
		return finalize_invitation(c, data, len);

	if(type != 0 || len != 18 || c->status.invitation_used)
		return false;

	// Recover the filename from the cookie and the key
	digest_t *digest = digest_open_by_name("sha256", 18);
	if(!digest)
		abort();
	char *fingerprint = ecdsa_get_base64_public_key(invitation_key);
	char hashbuf[18 + strlen(fingerprint)];
	char cookie[25];
	memcpy(hashbuf, data, 18);
	memcpy(hashbuf + 18, fingerprint, sizeof hashbuf - 18);
	digest_create(digest, hashbuf, sizeof hashbuf, cookie);
	b64encode_urlsafe(cookie, cookie, 18);
	digest_close(digest);
	free(fingerprint);

	char filename[PATH_MAX], usedname[PATH_MAX];
	snprintf(filename, sizeof filename, "%s" SLASH "invitations" SLASH "%s", confbase, cookie);
	snprintf(usedname, sizeof usedname, "%s" SLASH "invitations" SLASH "%s.used", confbase, cookie);

	// Atomically rename the invitation file
	if(rename(filename, usedname)) {
		if(errno == ENOENT)
			logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s tried to use non-existing invitation %s\n", c->hostname, cookie);
		else
			logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to rename invitation %s\n", cookie);
		return false;
	}

	// Open the renamed file
	FILE *f = fopen(usedname, "r");
	if(!f) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to open invitation %s\n", cookie);
		return false;
	}

	// Read the new node's Name from the file
	char buf[1024];
	fgets(buf, sizeof buf, f);
	if(*buf)
		buf[strlen(buf) - 1] = 0;

	len = strcspn(buf, " \t=");
	char *name = buf + len;
	name += strspn(name, " \t");
	if(*name == '=') {
		name++;
		name += strspn(name, " \t");
	}
	buf[len] = 0;

	if(!*buf || !*name || strcasecmp(buf, "Name") || !check_id(name)) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Invalid invitation file %s\n", cookie);
		fclose(f);
		return false;
	}

	free(c->name);
	c->name = xstrdup(name);

	// Send the node the contents of the invitation file
	rewind(f);
	size_t result;
	while((result = fread(buf, 1, sizeof buf, f)))
		sptps_send_record(&c->sptps, 0, buf, result);
	sptps_send_record(&c->sptps, 1, buf, 0);
	fclose(f);
	unlink(usedname);

	c->status.invitation_used = true;

	logger(DEBUG_CONNECTIONS, LOG_INFO, "Invitation %s succesfully sent to %s (%s)", cookie, c->name, c->hostname);
	return true;
}
Exemple #4
0
static bool finalize_join(void) {
	char *name = xstrdup(get_value(data, "Name"));
	if(!name) {
		fprintf(stderr, "No Name found in invitation!\n");
		return false;
	}

	if(!check_id(name)) {
		fprintf(stderr, "Invalid Name found in invitation: %s!\n", name);
		return false;
	}

	if(!netname)
		netname = grep(data, "NetName");

	bool ask_netname = false;
	char temp_netname[32];

make_names:
	if(!confbasegiven) {
		free(confbase);
		confbase = NULL;
	}

	make_names(false);

	free(tinc_conf);
	free(hosts_dir);

	xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
	xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);

	if(!access(tinc_conf, F_OK)) {
		fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
		if(confbasegiven)
			return false;

		// Generate a random netname, ask for a better one later.
		ask_netname = true;
		snprintf(temp_netname, sizeof temp_netname, "join_%x", rand());
		netname = temp_netname;
		goto make_names;
	}	

	if(mkdir(confbase, 0777) && errno != EEXIST) {
		fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
		return false;
	}

	if(mkdir(hosts_dir, 0777) && errno != EEXIST) {
		fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno));
		return false;
	}

	FILE *f = fopen(tinc_conf, "w");
	if(!f) {
		fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno));
		return false;
	}

	fprintf(f, "Name = %s\n", name);

	char filename[PATH_MAX];
	snprintf(filename, sizeof filename, "%s" SLASH "%s", hosts_dir, name);
	FILE *fh = fopen(filename, "w");
	if(!fh) {
		fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
		fclose(f);
		return false;
	}

	// Filter first chunk on approved keywords, split between tinc.conf and hosts/Name
	// Other chunks go unfiltered to their respective host config files
	const char *p = data;
	char *l, *value;

	while((l = get_line(&p))) {
		// Ignore comments
		if(*l == '#')
			continue;

		// Split line into variable and value
		int len = strcspn(l, "\t =");
		value = l + len;
		value += strspn(value, "\t ");
		if(*value == '=') {
			value++;
			value += strspn(value, "\t ");
		}
		l[len] = 0;

		// Is it a Name?
		if(!strcasecmp(l, "Name"))
			if(strcmp(value, name))
				break;
			else
				continue;
		else if(!strcasecmp(l, "NetName"))
			continue;

		// Check the list of known variables
		bool found = false;
		int i;
		for(i = 0; variables[i].name; i++) {
			if(strcasecmp(l, variables[i].name))
				continue;
			found = true;
			break;
		}

		// Ignore unknown and unsafe variables
		if(!found) {
			fprintf(stderr, "Ignoring unknown variable '%s' in invitation.\n", l);
			continue;
		} else if(!(variables[i].type & VAR_SAFE)) {
			fprintf(stderr, "Ignoring unsafe variable '%s' in invitation.\n", l);
			continue;
		}

		// Copy the safe variable to the right config file
		fprintf(variables[i].type & VAR_HOST ? fh : f, "%s = %s\n", l, value);
	}

	fclose(f);

	while(l && !strcasecmp(l, "Name")) {
		if(!check_id(value)) {
			fprintf(stderr, "Invalid Name found in invitation.\n");
			return false;
		}

		if(!strcmp(value, name)) {
			fprintf(stderr, "Secondary chunk would overwrite our own host config file.\n");
			return false;
		}

		snprintf(filename, sizeof filename, "%s" SLASH "%s", hosts_dir, value);
		f = fopen(filename, "w");

		if(!f) {
			fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
			return false;
		}

		while((l = get_line(&p))) {
			if(!strcmp(l, "#---------------------------------------------------------------#"))
				continue;
			int len = strcspn(l, "\t =");
			if(len == 4 && !strncasecmp(l, "Name", 4)) {
				value = l + len;
				value += strspn(value, "\t ");
				if(*value == '=') {
					value++;
					value += strspn(value, "\t ");
				}
				l[len] = 0;
				break;
			}

			fputs(l, f);
			fputc('\n', f);
		}

		fclose(f);
	}

	// Generate our key and send a copy to the server
	ecdsa_t *key = ecdsa_generate();
	if(!key)
		return false;

	char *b64key = ecdsa_get_base64_public_key(key);
	if(!b64key)
		return false;

	snprintf(filename, sizeof filename, "%s" SLASH "ed25519_key.priv", confbase);
	f = fopenmask(filename, "w", 0600);
	if(!f)
		return false;

	if(!ecdsa_write_pem_private_key(key, f)) {
		fprintf(stderr, "Error writing private key!\n");
		ecdsa_free(key);
		fclose(f);
		return false;
	}

	fclose(f);

	fprintf(fh, "Ed25519PublicKey = %s\n", b64key);

	sptps_send_record(&sptps, 1, b64key, strlen(b64key));
	free(b64key);
	ecdsa_free(key);

	check_port(name);

ask_netname:
	if(ask_netname && tty) {
		fprintf(stderr, "Enter a new netname: ");
		if(!fgets(line, sizeof line, stdin)) {
			fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
			return false;
		}
		if(!*line || *line == '\n')
			goto ask_netname;

		line[strlen(line) - 1] = 0;

		char newbase[PATH_MAX];
		snprintf(newbase, sizeof newbase, CONFDIR SLASH "tinc" SLASH "%s", line);
		if(rename(confbase, newbase)) {
			fprintf(stderr, "Error trying to rename %s to %s: %s\n", confbase, newbase, strerror(errno));
			goto ask_netname;
		}

		netname = line;
		make_names(false);
	}

	fprintf(stderr, "Configuration stored in: %s\n", confbase);

	return true;
}
Exemple #5
0
int main(int argc, char *argv[]) {
	program_name = argv[0];
	bool initiator = false;
	bool datagram = false;
#ifdef HAVE_LINUX
	bool tun = false;
#endif
	int packetloss = 0;
	int r;
	int option_index = 0;
	ecdsa_t *mykey = NULL, *hiskey = NULL;
	bool quit = false;

	while((r = getopt_long(argc, argv, "dqrtwL:W:v", long_options, &option_index)) != EOF) {
		switch (r) {
			case 0:   /* long option */
				break;

			case 'd': /* datagram mode */
				datagram = true;
				break;

			case 'q': /* close connection on EOF from stdin */
				quit = true;
				break;

			case 'r': /* read only */
				readonly = true;
				break;

			case 't': /* read only */
#ifdef HAVE_LINUX
				tun = true;
#else
				fprintf(stderr, "--tun is only supported on Linux.\n");
				usage();
				return 1;
#endif
				break;

			case 'w': /* write only */
				writeonly = true;
				break;

			case 'L': /* packet loss rate */
				packetloss = atoi(optarg);
				break;

			case 'W': /* replay window size */
				sptps_replaywin = atoi(optarg);
				break;

			case 'v': /* be verbose */
				verbose = true;
				break;

			case '?': /* wrong options */
				usage();
				return 1;

			case 1: /* help */
				usage();
				return 0;

			default:
				break;
		}
	}

	argc -= optind - 1;
	argv += optind - 1;

	if(argc < 4 || argc > 5) {
		fprintf(stderr, "Wrong number of arguments.\n");
		usage();
		return 1;
	}

	if(argc > 4)
		initiator = true;

	srand(time(NULL));

#ifdef HAVE_LINUX
	if(tun) {
		in = out = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
		if(in < 0) {
			fprintf(stderr, "Could not open tun device: %s\n", strerror(errno));
			return 1;
		}
		struct ifreq ifr = {
			.ifr_flags = IFF_TUN
		};
		if(ioctl(in, TUNSETIFF, &ifr)) {
			fprintf(stderr, "Could not configure tun interface: %s\n", strerror(errno));
			return 1;
		}
		ifr.ifr_name[IFNAMSIZ - 1] = 0;
		fprintf(stderr, "Using tun interface %s\n", ifr.ifr_name);
	}
#endif

#ifdef HAVE_MINGW
	static struct WSAData wsa_state;
	if(WSAStartup(MAKEWORD(2, 2), &wsa_state))
		return 1;
#endif

	struct addrinfo *ai, hint;
	memset(&hint, 0, sizeof hint);

	hint.ai_family = AF_UNSPEC;
	hint.ai_socktype = datagram ? SOCK_DGRAM : SOCK_STREAM;
	hint.ai_protocol = datagram ? IPPROTO_UDP : IPPROTO_TCP;
	hint.ai_flags = initiator ? 0 : AI_PASSIVE;

	if(getaddrinfo(initiator ? argv[3] : NULL, initiator ? argv[4] : argv[3], &hint, &ai) || !ai) {
		fprintf(stderr, "getaddrinfo() failed: %s\n", sockstrerror(sockerrno));
		return 1;
	}

	int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
	if(sock < 0) {
		fprintf(stderr, "Could not create socket: %s\n", sockstrerror(sockerrno));
		return 1;
	}

	int one = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one);

	if(initiator) {
		if(connect(sock, ai->ai_addr, ai->ai_addrlen)) {
			fprintf(stderr, "Could not connect to peer: %s\n", sockstrerror(sockerrno));
			return 1;
		}
		fprintf(stderr, "Connected\n");
	} else {
		if(bind(sock, ai->ai_addr, ai->ai_addrlen)) {
			fprintf(stderr, "Could not bind socket: %s\n", sockstrerror(sockerrno));
			return 1;
		}

		if(!datagram) {
			if(listen(sock, 1)) {
				fprintf(stderr, "Could not listen on socket: %s\n", sockstrerror(sockerrno));
				return 1;
			}
			fprintf(stderr, "Listening...\n");

			sock = accept(sock, NULL, NULL);
			if(sock < 0) {
				fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno));
				return 1;
			}
		} else {
			fprintf(stderr, "Listening...\n");

			char buf[65536];
			struct sockaddr addr;
			socklen_t addrlen = sizeof addr;

			if(recvfrom(sock, buf, sizeof buf, MSG_PEEK, &addr, &addrlen) <= 0) {
				fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno));
				return 1;
			}

			if(connect(sock, &addr, addrlen)) {
				fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno));
				return 1;
			}
		}

		fprintf(stderr, "Connected\n");
	}

	crypto_init();

	FILE *fp = fopen(argv[1], "r");
	if(!fp) {
		fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno));
		return 1;
	}
	if(!(mykey = ecdsa_read_pem_private_key(fp)))
		return 1;
	fclose(fp);

	fp = fopen(argv[2], "r");
	if(!fp) {
		fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno));
		return 1;
	}
	if(!(hiskey = ecdsa_read_pem_public_key(fp)))
		return 1;
	fclose(fp);

	if(verbose)
		fprintf(stderr, "Keys loaded\n");

	sptps_t s;
	if(!sptps_start(&s, &sock, initiator, datagram, mykey, hiskey, "sptps_test", 10, send_data, receive_record))
		return 1;

	while(true) {
		if(writeonly && readonly)
			break;

		char buf[65535] = "";

		fd_set fds;
		FD_ZERO(&fds);
#ifndef HAVE_MINGW
		if(!readonly && s.instate)
			FD_SET(in, &fds);
#endif
		FD_SET(sock, &fds);
		if(select(sock + 1, &fds, NULL, NULL, NULL) <= 0)
			return 1;

		if(FD_ISSET(in, &fds)) {
			ssize_t len = read(in, buf, sizeof buf);
			if(len < 0) {
				fprintf(stderr, "Could not read from stdin: %s\n", strerror(errno));
				return 1;
			}
			if(len == 0) {
				if(quit)
					break;
				readonly = true;
				continue;
			}
			if(buf[0] == '#')
				s.outseqno = atoi(buf + 1);
			if(buf[0] == '^')
				sptps_send_record(&s, SPTPS_HANDSHAKE, NULL, 0);
			else if(buf[0] == '$') {
				sptps_force_kex(&s);
				if(len > 1)
					sptps_send_record(&s, 0, buf, len);
			} else
			if(!sptps_send_record(&s, buf[0] == '!' ? 1 : 0, buf, (len == 1 && buf[0] == '\n') ? 0 : buf[0] == '*' ? sizeof buf : len))
				return 1;
		}

		if(FD_ISSET(sock, &fds)) {
			ssize_t len = recv(sock, buf, sizeof buf, 0);
			if(len < 0) {
				fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno));
				return 1;
			}
			if(len == 0) {
				fprintf(stderr, "Connection terminated by peer.\n");
				break;
			}
			if(verbose) {
				char hex[len * 2 + 1];
				bin2hex(buf, hex, len);
				fprintf(stderr, "Received %d bytes of data:\n%s\n", (int)len, hex);
			}
			if(packetloss && (rand() % 100) < packetloss) {
				if(verbose)
					fprintf(stderr, "Dropped.\n");
				continue;
			}
			char *bufp = buf;
			while(len) {
				size_t done = sptps_receive_data(&s, bufp, len);
				if(!done) {
					if(!datagram)
						return 1;
				} else {
					break;
				}

				bufp += done;
				len -= done;
			}
		}
	}

	if(!sptps_stop(&s))
		return 1;

	return 0;
}
Exemple #6
0
int main(int argc, char *argv[]) {
	ecdsa_t *key1, *key2;
	ecdh_t *ecdh1, *ecdh2;
	sptps_t sptps1, sptps2;
	char buf1[4096], buf2[4096], buf3[4096];
	double duration = argc > 1 ? atof(argv[1]) : 10;

	crypto_init();

	randomize(buf1, sizeof buf1);
	randomize(buf2, sizeof buf2);
	randomize(buf3, sizeof buf3);

	// Key generation

	fprintf(stderr, "Generating keys for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);)
		ecdsa_free(ecdsa_generate());
	fprintf(stderr, "%17.2lf op/s\n", rate);

	key1 = ecdsa_generate();
	key2 = ecdsa_generate();

	// Ed25519 signatures

	fprintf(stderr, "Ed25519 sign for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);)
		if(!ecdsa_sign(key1, buf1, 256, buf2))
			return 1;
	fprintf(stderr, "%20.2lf op/s\n", rate);

	fprintf(stderr, "Ed25519 verify for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);)
		if(!ecdsa_verify(key1, buf1, 256, buf2)) {
			fprintf(stderr, "Signature verification failed\n");
			return 1;
		}
	fprintf(stderr, "%18.2lf op/s\n", rate);

	ecdh1 = ecdh_generate_public(buf1);
	fprintf(stderr, "ECDH for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		ecdh2 = ecdh_generate_public(buf2);
		if(!ecdh2)
			return 1;
		if(!ecdh_compute_shared(ecdh2, buf1, buf3))
			return 1;
	}
	fprintf(stderr, "%28.2lf op/s\n", rate);
	ecdh_free(ecdh1);

	// SPTPS authentication phase

	int fd[2];
	if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) {
		fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno));
		return 1;
	}

	struct pollfd pfd[2] = {{fd[0], POLLIN}, {fd[1], POLLIN}};

	fprintf(stderr, "SPTPS/TCP authenticate for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		sptps_start(&sptps1, fd + 0, true, false, key1, key2, "sptps_speed", 11, send_data, receive_record);
		sptps_start(&sptps2, fd + 1, false, false, key2, key1, "sptps_speed", 11, send_data, receive_record);
		while(poll(pfd, 2, 0)) {
			if(pfd[0].revents)
				receive_data(&sptps1);
			if(pfd[1].revents)
				receive_data(&sptps2);
		}
		sptps_stop(&sptps1);
		sptps_stop(&sptps2);
	}
	fprintf(stderr, "%10.2lf op/s\n", rate * 2);

	// SPTPS data

	sptps_start(&sptps1, fd + 0, true, false, key1, key2, "sptps_speed", 11, send_data, receive_record);
	sptps_start(&sptps2, fd + 1, false, false, key2, key1, "sptps_speed", 11, send_data, receive_record);
	while(poll(pfd, 2, 0)) {
		if(pfd[0].revents)
			receive_data(&sptps1);
		if(pfd[1].revents)
			receive_data(&sptps2);
	}
	fprintf(stderr, "SPTPS/TCP transmit for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		if(!sptps_send_record(&sptps1, 0, buf1, 1451))
			abort();
		receive_data(&sptps2);
	}
	rate *= 2 * 1451 * 8;
	if(rate > 1e9)
		fprintf(stderr, "%14.2lf Gbit/s\n", rate / 1e9);
	else if(rate > 1e6)
		fprintf(stderr, "%14.2lf Mbit/s\n", rate / 1e6);
	else if(rate > 1e3)
		fprintf(stderr, "%14.2lf kbit/s\n", rate / 1e3);
	sptps_stop(&sptps1);
	sptps_stop(&sptps2);

	// SPTPS datagram authentication phase

	close(fd[0]);
	close(fd[1]);

	if(socketpair(AF_UNIX, SOCK_DGRAM, 0, fd)) {
		fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno));
		return 1;
	}

	fprintf(stderr, "SPTPS/UDP authenticate for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		sptps_start(&sptps1, fd + 0, true, true, key1, key2, "sptps_speed", 11, send_data, receive_record);
		sptps_start(&sptps2, fd + 1, false, true, key2, key1, "sptps_speed", 11, send_data, receive_record);
		while(poll(pfd, 2, 0)) {
			if(pfd[0].revents)
				receive_data(&sptps1);
			if(pfd[1].revents)
				receive_data(&sptps2);
		}
		sptps_stop(&sptps1);
		sptps_stop(&sptps2);
	}
	fprintf(stderr, "%10.2lf op/s\n", rate * 2);

	// SPTPS datagram data

	sptps_start(&sptps1, fd + 0, true, true, key1, key2, "sptps_speed", 11, send_data, receive_record);
	sptps_start(&sptps2, fd + 1, false, true, key2, key1, "sptps_speed", 11, send_data, receive_record);
	while(poll(pfd, 2, 0)) {
		if(pfd[0].revents)
			receive_data(&sptps1);
		if(pfd[1].revents)
			receive_data(&sptps2);
	}
	fprintf(stderr, "SPTPS/UDP transmit for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		if(!sptps_send_record(&sptps1, 0, buf1, 1451))
			abort();
		receive_data(&sptps2);
	}
	rate *= 2 * 1451 * 8;
	if(rate > 1e9)
		fprintf(stderr, "%14.2lf Gbit/s\n", rate / 1e9);
	else if(rate > 1e6)
		fprintf(stderr, "%14.2lf Mbit/s\n", rate / 1e6);
	else if(rate > 1e3)
		fprintf(stderr, "%14.2lf kbit/s\n", rate / 1e3);
	sptps_stop(&sptps1);
	sptps_stop(&sptps2);

	// Clean up

	close(fd[0]);
	close(fd[1]);
	ecdsa_free(key1);
	ecdsa_free(key2);
	crypto_exit();

	return 0;
}