proto_tcp.c revision 218194
1/*-
2 * Copyright (c) 2009-2010 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Pawel Jakub Dawidek under sponsorship from
6 * 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 218194 2011-02-02 15:53:09Z 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, 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(HASTD_PORT);
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, &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	/* Socket settings. */
201	nodelay = 1;
202	if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
203	    sizeof(nodelay)) == -1) {
204		pjdlog_errno(LOG_WARNING, "Unable to set TCP_NOELAY");
205	}
206
207	tctx->tc_side = side;
208	tctx->tc_magic = TCP4_CTX_MAGIC;
209	*ctxp = tctx;
210
211	return (0);
212}
213
214static int
215tcp4_setup_wrap(int fd, int side, void **ctxp)
216{
217	struct tcp4_ctx *tctx;
218
219	PJDLOG_ASSERT(fd >= 0);
220	PJDLOG_ASSERT(side == TCP4_SIDE_CLIENT ||
221	    side == TCP4_SIDE_SERVER_WORK);
222	PJDLOG_ASSERT(ctxp != NULL);
223
224	tctx = malloc(sizeof(*tctx));
225	if (tctx == NULL)
226		return (errno);
227
228	tctx->tc_fd = fd;
229	tctx->tc_sin.sin_family = AF_UNSPEC;
230	tctx->tc_side = side;
231	tctx->tc_magic = TCP4_CTX_MAGIC;
232	*ctxp = tctx;
233
234	return (0);
235}
236
237static int
238tcp4_client(const char *addr, void **ctxp)
239{
240
241	return (tcp4_setup_new(addr, TCP4_SIDE_CLIENT, ctxp));
242}
243
244static int
245tcp4_connect(void *ctx, int timeout)
246{
247	struct tcp4_ctx *tctx = ctx;
248	int error, flags;
249
250	PJDLOG_ASSERT(tctx != NULL);
251	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
252	PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_CLIENT);
253	PJDLOG_ASSERT(tctx->tc_fd >= 0);
254	PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC);
255	PJDLOG_ASSERT(timeout >= -1);
256
257	flags = fcntl(tctx->tc_fd, F_GETFL);
258	if (flags == -1) {
259		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
260		    "fcntl(F_GETFL) failed"));
261		return (errno);
262	}
263	/*
264	 * We make socket non-blocking so we can handle connection timeout
265	 * manually.
266	 */
267	flags |= O_NONBLOCK;
268	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
269		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
270		    "fcntl(F_SETFL, O_NONBLOCK) failed"));
271		return (errno);
272	}
273
274	if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
275	    sizeof(tctx->tc_sin)) == 0) {
276		if (timeout == -1)
277			return (0);
278		error = 0;
279		goto done;
280	}
281	if (errno != EINPROGRESS) {
282		error = errno;
283		pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed");
284		goto done;
285	}
286	if (timeout == -1)
287		return (0);
288	return (tcp4_connect_wait(ctx, timeout));
289done:
290	flags &= ~O_NONBLOCK;
291	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
292		if (error == 0)
293			error = errno;
294		pjdlog_common(LOG_DEBUG, 1, errno,
295		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
296	}
297	return (error);
298}
299
300static int
301tcp4_connect_wait(void *ctx, int timeout)
302{
303	struct tcp4_ctx *tctx = ctx;
304	struct timeval tv;
305	fd_set fdset;
306	socklen_t esize;
307	int error, flags, ret;
308
309	PJDLOG_ASSERT(tctx != NULL);
310	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
311	PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_CLIENT);
312	PJDLOG_ASSERT(tctx->tc_fd >= 0);
313	PJDLOG_ASSERT(timeout >= 0);
314
315	tv.tv_sec = timeout;
316	tv.tv_usec = 0;
317again:
318	FD_ZERO(&fdset);
319	FD_SET(tctx->tc_fd, &fdset);
320	ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv);
321	if (ret == 0) {
322		error = ETIMEDOUT;
323		goto done;
324	} else if (ret == -1) {
325		if (errno == EINTR)
326			goto again;
327		error = errno;
328		pjdlog_common(LOG_DEBUG, 1, errno, "select() failed");
329		goto done;
330	}
331	PJDLOG_ASSERT(ret > 0);
332	PJDLOG_ASSERT(FD_ISSET(tctx->tc_fd, &fdset));
333	esize = sizeof(error);
334	if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error,
335	    &esize) == -1) {
336		error = errno;
337		pjdlog_common(LOG_DEBUG, 1, errno,
338		    "getsockopt(SO_ERROR) failed");
339		goto done;
340	}
341	if (error != 0) {
342		pjdlog_common(LOG_DEBUG, 1, error,
343		    "getsockopt(SO_ERROR) returned error");
344		goto done;
345	}
346	error = 0;
347done:
348	flags = fcntl(tctx->tc_fd, F_GETFL);
349	if (flags == -1) {
350		if (error == 0)
351			error = errno;
352		pjdlog_common(LOG_DEBUG, 1, errno, "fcntl(F_GETFL) failed");
353		return (error);
354	}
355	flags &= ~O_NONBLOCK;
356	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
357		if (error == 0)
358			error = errno;
359		pjdlog_common(LOG_DEBUG, 1, errno,
360		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
361	}
362	return (error);
363}
364
365static int
366tcp4_server(const char *addr, void **ctxp)
367{
368	struct tcp4_ctx *tctx;
369	int ret, val;
370
371	ret = tcp4_setup_new(addr, TCP4_SIDE_SERVER_LISTEN, ctxp);
372	if (ret != 0)
373		return (ret);
374
375	tctx = *ctxp;
376
377	val = 1;
378	/* Ignore failure. */
379	(void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val,
380	   sizeof(val));
381
382	PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC);
383
384	if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
385	    sizeof(tctx->tc_sin)) < 0) {
386		ret = errno;
387		tcp4_close(tctx);
388		return (ret);
389	}
390	if (listen(tctx->tc_fd, 8) < 0) {
391		ret = errno;
392		tcp4_close(tctx);
393		return (ret);
394	}
395
396	return (0);
397}
398
399static int
400tcp4_accept(void *ctx, void **newctxp)
401{
402	struct tcp4_ctx *tctx = ctx;
403	struct tcp4_ctx *newtctx;
404	socklen_t fromlen;
405	int ret;
406
407	PJDLOG_ASSERT(tctx != NULL);
408	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
409	PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_SERVER_LISTEN);
410	PJDLOG_ASSERT(tctx->tc_fd >= 0);
411	PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC);
412
413	newtctx = malloc(sizeof(*newtctx));
414	if (newtctx == NULL)
415		return (errno);
416
417	fromlen = sizeof(tctx->tc_sin);
418	newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
419	    &fromlen);
420	if (newtctx->tc_fd < 0) {
421		ret = errno;
422		free(newtctx);
423		return (ret);
424	}
425
426	newtctx->tc_side = TCP4_SIDE_SERVER_WORK;
427	newtctx->tc_magic = TCP4_CTX_MAGIC;
428	*newctxp = newtctx;
429
430	return (0);
431}
432
433static int
434tcp4_wrap(int fd, bool client, void **ctxp)
435{
436
437	return (tcp4_setup_wrap(fd,
438	    client ? TCP4_SIDE_CLIENT : TCP4_SIDE_SERVER_WORK, ctxp));
439}
440
441static int
442tcp4_send(void *ctx, const unsigned char *data, size_t size, int fd)
443{
444	struct tcp4_ctx *tctx = ctx;
445
446	PJDLOG_ASSERT(tctx != NULL);
447	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
448	PJDLOG_ASSERT(tctx->tc_fd >= 0);
449	PJDLOG_ASSERT(fd == -1);
450
451	return (proto_common_send(tctx->tc_fd, data, size, -1));
452}
453
454static int
455tcp4_recv(void *ctx, unsigned char *data, size_t size, int *fdp)
456{
457	struct tcp4_ctx *tctx = ctx;
458
459	PJDLOG_ASSERT(tctx != NULL);
460	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
461	PJDLOG_ASSERT(tctx->tc_fd >= 0);
462	PJDLOG_ASSERT(fdp == NULL);
463
464	return (proto_common_recv(tctx->tc_fd, data, size, NULL));
465}
466
467static int
468tcp4_descriptor(const void *ctx)
469{
470	const struct tcp4_ctx *tctx = ctx;
471
472	PJDLOG_ASSERT(tctx != NULL);
473	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
474
475	return (tctx->tc_fd);
476}
477
478static void
479sin2str(struct sockaddr_in *sinp, char *addr, size_t size)
480{
481	in_addr_t ip;
482	unsigned int port;
483
484	PJDLOG_ASSERT(addr != NULL);
485	PJDLOG_ASSERT(sinp->sin_family == AF_INET);
486
487	ip = ntohl(sinp->sin_addr.s_addr);
488	port = ntohs(sinp->sin_port);
489	PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%u.%u.%u.%u:%u",
490	    ((ip >> 24) & 0xff), ((ip >> 16) & 0xff), ((ip >> 8) & 0xff),
491	    (ip & 0xff), port) < (ssize_t)size);
492}
493
494static bool
495tcp4_address_match(const void *ctx, const char *addr)
496{
497	const struct tcp4_ctx *tctx = ctx;
498	struct sockaddr_in sin;
499	socklen_t sinlen;
500	in_addr_t ip1, ip2;
501
502	PJDLOG_ASSERT(tctx != NULL);
503	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
504
505	if (tcp4_addr(addr, &sin) != 0)
506		return (false);
507	ip1 = sin.sin_addr.s_addr;
508
509	sinlen = sizeof(sin);
510	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0)
511		return (false);
512	ip2 = sin.sin_addr.s_addr;
513
514	return (ip1 == ip2);
515}
516
517static void
518tcp4_local_address(const void *ctx, char *addr, size_t size)
519{
520	const struct tcp4_ctx *tctx = ctx;
521	struct sockaddr_in sin;
522	socklen_t sinlen;
523
524	PJDLOG_ASSERT(tctx != NULL);
525	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
526
527	sinlen = sizeof(sin);
528	if (getsockname(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
529		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
530		return;
531	}
532	sin2str(&sin, addr, size);
533}
534
535static void
536tcp4_remote_address(const void *ctx, char *addr, size_t size)
537{
538	const struct tcp4_ctx *tctx = ctx;
539	struct sockaddr_in sin;
540	socklen_t sinlen;
541
542	PJDLOG_ASSERT(tctx != NULL);
543	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
544
545	sinlen = sizeof(sin);
546	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
547		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
548		return;
549	}
550	sin2str(&sin, addr, size);
551}
552
553static void
554tcp4_close(void *ctx)
555{
556	struct tcp4_ctx *tctx = ctx;
557
558	PJDLOG_ASSERT(tctx != NULL);
559	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
560
561	if (tctx->tc_fd >= 0)
562		close(tctx->tc_fd);
563	tctx->tc_magic = 0;
564	free(tctx);
565}
566
567static struct hast_proto tcp4_proto = {
568	.hp_name = "tcp4",
569	.hp_client = tcp4_client,
570	.hp_connect = tcp4_connect,
571	.hp_connect_wait = tcp4_connect_wait,
572	.hp_server = tcp4_server,
573	.hp_accept = tcp4_accept,
574	.hp_wrap = tcp4_wrap,
575	.hp_send = tcp4_send,
576	.hp_recv = tcp4_recv,
577	.hp_descriptor = tcp4_descriptor,
578	.hp_address_match = tcp4_address_match,
579	.hp_local_address = tcp4_local_address,
580	.hp_remote_address = tcp4_remote_address,
581	.hp_close = tcp4_close
582};
583
584static __constructor void
585tcp4_ctor(void)
586{
587
588	proto_register(&tcp4_proto, true);
589}
590