proto_tcp.c revision 219864
1139731Simp/*-
2232491Stijl * Copyright (c) 2009-2010 The FreeBSD Foundation
34Srgrimes * All rights reserved.
4232491Stijl *
54Srgrimes * This software was developed by Pawel Jakub Dawidek under sponsorship from
6232491Stijl * the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD: head/sbin/hastd/proto_tcp4.c 219864 2011-03-22 10:39:34Z pjd $");
32
33#include <sys/param.h>	/* MAXHOSTNAMELEN */
34
35#include <netinet/in.h>
36#include <netinet/tcp.h>
37
38#include <errno.h>
39#include <fcntl.h>
40#include <netdb.h>
41#include <stdbool.h>
42#include <stdint.h>
43#include <stdio.h>
44#include <string.h>
45#include <unistd.h>
46
47#include "hast.h"
48#include "pjdlog.h"
49#include "proto_impl.h"
50#include "subr.h"
51
52#define	TCP4_CTX_MAGIC	0x7c441c
53struct tcp4_ctx {
54	int			tc_magic;
55	struct sockaddr_in	tc_sin;
56	int			tc_fd;
57	int			tc_side;
58#define	TCP4_SIDE_CLIENT	0
59#define	TCP4_SIDE_SERVER_LISTEN	1
60#define	TCP4_SIDE_SERVER_WORK	2
61};
62
63static int tcp4_connect_wait(void *ctx, int timeout);
64static void tcp4_close(void *ctx);
65
66static in_addr_t
67str2ip(const char *str)
68{
69	struct hostent *hp;
70	in_addr_t ip;
71
72	ip = inet_addr(str);
73	if (ip != INADDR_NONE) {
74		/* It is a valid IP address. */
75		return (ip);
76	}
77	/* Check if it is a valid host name. */
78	hp = gethostbyname(str);
79	if (hp == NULL)
80		return (INADDR_NONE);
81	return (((struct in_addr *)(void *)hp->h_addr)->s_addr);
82}
83
84/*
85 * Function converts the given string to unsigned number.
86 */
87static int
88numfromstr(const char *str, intmax_t minnum, intmax_t maxnum, intmax_t *nump)
89{
90	intmax_t digit, num;
91
92	if (str[0] == '\0')
93		goto invalid;	/* Empty string. */
94	num = 0;
95	for (; *str != '\0'; str++) {
96		if (*str < '0' || *str > '9')
97			goto invalid;	/* Non-digit character. */
98		digit = *str - '0';
99		if (num > num * 10 + digit)
100			goto invalid;	/* Overflow. */
101		num = num * 10 + digit;
102		if (num > maxnum)
103			goto invalid;	/* Too big. */
104	}
105	if (num < minnum)
106		goto invalid;	/* Too small. */
107	*nump = num;
108	return (0);
109invalid:
110	errno = EINVAL;
111	return (-1);
112}
113
114static int
115tcp4_addr(const char *addr, int defport, struct sockaddr_in *sinp)
116{
117	char iporhost[MAXHOSTNAMELEN];
118	const char *pp;
119	size_t size;
120	in_addr_t ip;
121
122	if (addr == NULL)
123		return (-1);
124
125	if (strncasecmp(addr, "tcp4://", 7) == 0)
126		addr += 7;
127	else if (strncasecmp(addr, "tcp://", 6) == 0)
128		addr += 6;
129	else {
130		/*
131		 * Because TCP4 is the default assume IP or host is given without
132		 * prefix.
133		 */
134	}
135
136	sinp->sin_family = AF_INET;
137	sinp->sin_len = sizeof(*sinp);
138	/* Extract optional port. */
139	pp = strrchr(addr, ':');
140	if (pp == NULL) {
141		/* Port not given, use the default. */
142		sinp->sin_port = htons(defport);
143	} else {
144		intmax_t port;
145
146		if (numfromstr(pp + 1, 1, 65535, &port) < 0)
147			return (errno);
148		sinp->sin_port = htons(port);
149	}
150	/* Extract host name or IP address. */
151	if (pp == NULL) {
152		size = sizeof(iporhost);
153		if (strlcpy(iporhost, addr, size) >= size)
154			return (ENAMETOOLONG);
155	} else {
156		size = (size_t)(pp - addr + 1);
157		if (size > sizeof(iporhost))
158			return (ENAMETOOLONG);
159		(void)strlcpy(iporhost, addr, size);
160	}
161	/* Convert string (IP address or host name) to in_addr_t. */
162	ip = str2ip(iporhost);
163	if (ip == INADDR_NONE)
164		return (EINVAL);
165	sinp->sin_addr.s_addr = ip;
166
167	return (0);
168}
169
170static int
171tcp4_setup_new(const char *addr, int side, void **ctxp)
172{
173	struct tcp4_ctx *tctx;
174	int ret, nodelay;
175
176	PJDLOG_ASSERT(addr != NULL);
177	PJDLOG_ASSERT(side == TCP4_SIDE_CLIENT ||
178	    side == TCP4_SIDE_SERVER_LISTEN);
179	PJDLOG_ASSERT(ctxp != NULL);
180
181	tctx = malloc(sizeof(*tctx));
182	if (tctx == NULL)
183		return (errno);
184
185	/* Parse given address. */
186	if ((ret = tcp4_addr(addr, HASTD_PORT, &tctx->tc_sin)) != 0) {
187		free(tctx);
188		return (ret);
189	}
190
191	PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC);
192
193	tctx->tc_fd = socket(AF_INET, SOCK_STREAM, 0);
194	if (tctx->tc_fd == -1) {
195		ret = errno;
196		free(tctx);
197		return (ret);
198	}
199
200	PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC);
201
202	/* Socket settings. */
203	nodelay = 1;
204	if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
205	    sizeof(nodelay)) == -1) {
206		pjdlog_errno(LOG_WARNING, "Unable to set TCP_NOELAY");
207	}
208
209	tctx->tc_side = side;
210	tctx->tc_magic = TCP4_CTX_MAGIC;
211	*ctxp = tctx;
212
213	return (0);
214}
215
216static int
217tcp4_setup_wrap(int fd, int side, void **ctxp)
218{
219	struct tcp4_ctx *tctx;
220
221	PJDLOG_ASSERT(fd >= 0);
222	PJDLOG_ASSERT(side == TCP4_SIDE_CLIENT ||
223	    side == TCP4_SIDE_SERVER_WORK);
224	PJDLOG_ASSERT(ctxp != NULL);
225
226	tctx = malloc(sizeof(*tctx));
227	if (tctx == NULL)
228		return (errno);
229
230	tctx->tc_fd = fd;
231	tctx->tc_sin.sin_family = AF_UNSPEC;
232	tctx->tc_side = side;
233	tctx->tc_magic = TCP4_CTX_MAGIC;
234	*ctxp = tctx;
235
236	return (0);
237}
238
239static int
240tcp4_client(const char *srcaddr, const char *dstaddr, void **ctxp)
241{
242	struct tcp4_ctx *tctx;
243	struct sockaddr_in sin;
244	int ret;
245
246	ret = tcp4_setup_new(dstaddr, TCP4_SIDE_CLIENT, ctxp);
247	if (ret != 0)
248		return (ret);
249	tctx = *ctxp;
250	if (srcaddr == NULL)
251		return (0);
252	ret = tcp4_addr(srcaddr, 0, &sin);
253	if (ret != 0) {
254		tcp4_close(tctx);
255		return (ret);
256	}
257	if (bind(tctx->tc_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
258		ret = errno;
259		tcp4_close(tctx);
260		return (ret);
261	}
262	return (0);
263}
264
265static int
266tcp4_connect(void *ctx, int timeout)
267{
268	struct tcp4_ctx *tctx = ctx;
269	int error, flags;
270
271	PJDLOG_ASSERT(tctx != NULL);
272	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
273	PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_CLIENT);
274	PJDLOG_ASSERT(tctx->tc_fd >= 0);
275	PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC);
276	PJDLOG_ASSERT(timeout >= -1);
277
278	flags = fcntl(tctx->tc_fd, F_GETFL);
279	if (flags == -1) {
280		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
281		    "fcntl(F_GETFL) failed"));
282		return (errno);
283	}
284	/*
285	 * We make socket non-blocking so we can handle connection timeout
286	 * manually.
287	 */
288	flags |= O_NONBLOCK;
289	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
290		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
291		    "fcntl(F_SETFL, O_NONBLOCK) failed"));
292		return (errno);
293	}
294
295	if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
296	    sizeof(tctx->tc_sin)) == 0) {
297		if (timeout == -1)
298			return (0);
299		error = 0;
300		goto done;
301	}
302	if (errno != EINPROGRESS) {
303		error = errno;
304		pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed");
305		goto done;
306	}
307	if (timeout == -1)
308		return (0);
309	return (tcp4_connect_wait(ctx, timeout));
310done:
311	flags &= ~O_NONBLOCK;
312	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
313		if (error == 0)
314			error = errno;
315		pjdlog_common(LOG_DEBUG, 1, errno,
316		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
317	}
318	return (error);
319}
320
321static int
322tcp4_connect_wait(void *ctx, int timeout)
323{
324	struct tcp4_ctx *tctx = ctx;
325	struct timeval tv;
326	fd_set fdset;
327	socklen_t esize;
328	int error, flags, ret;
329
330	PJDLOG_ASSERT(tctx != NULL);
331	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
332	PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_CLIENT);
333	PJDLOG_ASSERT(tctx->tc_fd >= 0);
334	PJDLOG_ASSERT(timeout >= 0);
335
336	tv.tv_sec = timeout;
337	tv.tv_usec = 0;
338again:
339	FD_ZERO(&fdset);
340	FD_SET(tctx->tc_fd, &fdset);
341	ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv);
342	if (ret == 0) {
343		error = ETIMEDOUT;
344		goto done;
345	} else if (ret == -1) {
346		if (errno == EINTR)
347			goto again;
348		error = errno;
349		pjdlog_common(LOG_DEBUG, 1, errno, "select() failed");
350		goto done;
351	}
352	PJDLOG_ASSERT(ret > 0);
353	PJDLOG_ASSERT(FD_ISSET(tctx->tc_fd, &fdset));
354	esize = sizeof(error);
355	if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error,
356	    &esize) == -1) {
357		error = errno;
358		pjdlog_common(LOG_DEBUG, 1, errno,
359		    "getsockopt(SO_ERROR) failed");
360		goto done;
361	}
362	if (error != 0) {
363		pjdlog_common(LOG_DEBUG, 1, error,
364		    "getsockopt(SO_ERROR) returned error");
365		goto done;
366	}
367	error = 0;
368done:
369	flags = fcntl(tctx->tc_fd, F_GETFL);
370	if (flags == -1) {
371		if (error == 0)
372			error = errno;
373		pjdlog_common(LOG_DEBUG, 1, errno, "fcntl(F_GETFL) failed");
374		return (error);
375	}
376	flags &= ~O_NONBLOCK;
377	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
378		if (error == 0)
379			error = errno;
380		pjdlog_common(LOG_DEBUG, 1, errno,
381		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
382	}
383	return (error);
384}
385
386static int
387tcp4_server(const char *addr, void **ctxp)
388{
389	struct tcp4_ctx *tctx;
390	int ret, val;
391
392	ret = tcp4_setup_new(addr, TCP4_SIDE_SERVER_LISTEN, ctxp);
393	if (ret != 0)
394		return (ret);
395
396	tctx = *ctxp;
397
398	val = 1;
399	/* Ignore failure. */
400	(void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val,
401	   sizeof(val));
402
403	PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC);
404
405	if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
406	    sizeof(tctx->tc_sin)) < 0) {
407		ret = errno;
408		tcp4_close(tctx);
409		return (ret);
410	}
411	if (listen(tctx->tc_fd, 8) < 0) {
412		ret = errno;
413		tcp4_close(tctx);
414		return (ret);
415	}
416
417	return (0);
418}
419
420static int
421tcp4_accept(void *ctx, void **newctxp)
422{
423	struct tcp4_ctx *tctx = ctx;
424	struct tcp4_ctx *newtctx;
425	socklen_t fromlen;
426	int ret;
427
428	PJDLOG_ASSERT(tctx != NULL);
429	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
430	PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_SERVER_LISTEN);
431	PJDLOG_ASSERT(tctx->tc_fd >= 0);
432	PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC);
433
434	newtctx = malloc(sizeof(*newtctx));
435	if (newtctx == NULL)
436		return (errno);
437
438	fromlen = sizeof(tctx->tc_sin);
439	newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
440	    &fromlen);
441	if (newtctx->tc_fd < 0) {
442		ret = errno;
443		free(newtctx);
444		return (ret);
445	}
446
447	newtctx->tc_side = TCP4_SIDE_SERVER_WORK;
448	newtctx->tc_magic = TCP4_CTX_MAGIC;
449	*newctxp = newtctx;
450
451	return (0);
452}
453
454static int
455tcp4_wrap(int fd, bool client, void **ctxp)
456{
457
458	return (tcp4_setup_wrap(fd,
459	    client ? TCP4_SIDE_CLIENT : TCP4_SIDE_SERVER_WORK, ctxp));
460}
461
462static int
463tcp4_send(void *ctx, const unsigned char *data, size_t size, int fd)
464{
465	struct tcp4_ctx *tctx = ctx;
466
467	PJDLOG_ASSERT(tctx != NULL);
468	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
469	PJDLOG_ASSERT(tctx->tc_fd >= 0);
470	PJDLOG_ASSERT(fd == -1);
471
472	return (proto_common_send(tctx->tc_fd, data, size, -1));
473}
474
475static int
476tcp4_recv(void *ctx, unsigned char *data, size_t size, int *fdp)
477{
478	struct tcp4_ctx *tctx = ctx;
479
480	PJDLOG_ASSERT(tctx != NULL);
481	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
482	PJDLOG_ASSERT(tctx->tc_fd >= 0);
483	PJDLOG_ASSERT(fdp == NULL);
484
485	return (proto_common_recv(tctx->tc_fd, data, size, NULL));
486}
487
488static int
489tcp4_descriptor(const void *ctx)
490{
491	const struct tcp4_ctx *tctx = ctx;
492
493	PJDLOG_ASSERT(tctx != NULL);
494	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
495
496	return (tctx->tc_fd);
497}
498
499static bool
500tcp4_address_match(const void *ctx, const char *addr)
501{
502	const struct tcp4_ctx *tctx = ctx;
503	struct sockaddr_in sin;
504	socklen_t sinlen;
505	in_addr_t ip1, ip2;
506
507	PJDLOG_ASSERT(tctx != NULL);
508	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
509
510	if (tcp4_addr(addr, HASTD_PORT, &sin) != 0)
511		return (false);
512	ip1 = sin.sin_addr.s_addr;
513
514	sinlen = sizeof(sin);
515	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0)
516		return (false);
517	ip2 = sin.sin_addr.s_addr;
518
519	return (ip1 == ip2);
520}
521
522static void
523tcp4_local_address(const void *ctx, char *addr, size_t size)
524{
525	const struct tcp4_ctx *tctx = ctx;
526	struct sockaddr_in sin;
527	socklen_t sinlen;
528
529	PJDLOG_ASSERT(tctx != NULL);
530	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
531
532	sinlen = sizeof(sin);
533	if (getsockname(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
534		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
535		return;
536	}
537	PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%S", &sin) < (ssize_t)size);
538}
539
540static void
541tcp4_remote_address(const void *ctx, char *addr, size_t size)
542{
543	const struct tcp4_ctx *tctx = ctx;
544	struct sockaddr_in sin;
545	socklen_t sinlen;
546
547	PJDLOG_ASSERT(tctx != NULL);
548	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
549
550	sinlen = sizeof(sin);
551	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
552		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
553		return;
554	}
555	PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%S", &sin) < (ssize_t)size);
556}
557
558static void
559tcp4_close(void *ctx)
560{
561	struct tcp4_ctx *tctx = ctx;
562
563	PJDLOG_ASSERT(tctx != NULL);
564	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
565
566	if (tctx->tc_fd >= 0)
567		close(tctx->tc_fd);
568	tctx->tc_magic = 0;
569	free(tctx);
570}
571
572static struct hast_proto tcp4_proto = {
573	.hp_name = "tcp4",
574	.hp_client = tcp4_client,
575	.hp_connect = tcp4_connect,
576	.hp_connect_wait = tcp4_connect_wait,
577	.hp_server = tcp4_server,
578	.hp_accept = tcp4_accept,
579	.hp_wrap = tcp4_wrap,
580	.hp_send = tcp4_send,
581	.hp_recv = tcp4_recv,
582	.hp_descriptor = tcp4_descriptor,
583	.hp_address_match = tcp4_address_match,
584	.hp_local_address = tcp4_local_address,
585	.hp_remote_address = tcp4_remote_address,
586	.hp_close = tcp4_close
587};
588
589static __constructor void
590tcp4_ctor(void)
591{
592
593	proto_register(&tcp4_proto, true);
594}
595