proto_tcp.c revision 210876
1189092Sed/*-
2189092Sed * Copyright (c) 2009-2010 The FreeBSD Foundation
3189092Sed * All rights reserved.
4189092Sed *
5189092Sed * This software was developed by Pawel Jakub Dawidek under sponsorship from
6189092Sed * the FreeBSD Foundation.
7189092Sed *
8189092Sed * Redistribution and use in source and binary forms, with or without
9189092Sed * modification, are permitted provided that the following conditions
10189092Sed * are met:
11189092Sed * 1. Redistributions of source code must retain the above copyright
12189092Sed *    notice, this list of conditions and the following disclaimer.
13189092Sed * 2. Redistributions in binary form must reproduce the above copyright
14189092Sed *    notice, this list of conditions and the following disclaimer in the
15189092Sed *    documentation and/or other materials provided with the distribution.
16189092Sed *
17189092Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18189092Sed * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19189092Sed * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20189092Sed * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21189092Sed * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22189092Sed * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23189092Sed * 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 210876 2010-08-05 18:27:41Z pjd $");
32
33#include <sys/param.h>	/* MAXHOSTNAMELEN */
34
35#include <netinet/in.h>
36#include <netinet/tcp.h>
37
38#include <assert.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <netdb.h>
42#include <stdbool.h>
43#include <stdint.h>
44#include <stdio.h>
45#include <string.h>
46#include <unistd.h>
47
48#include "hast.h"
49#include "pjdlog.h"
50#include "proto_impl.h"
51#include "subr.h"
52
53#define	TCP4_CTX_MAGIC	0x7c441c
54struct tcp4_ctx {
55	int			tc_magic;
56	struct sockaddr_in	tc_sin;
57	int			tc_fd;
58	int			tc_side;
59#define	TCP4_SIDE_CLIENT	0
60#define	TCP4_SIDE_SERVER_LISTEN	1
61#define	TCP4_SIDE_SERVER_WORK	2
62};
63
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		if (strlcpy(iporhost, addr, size) >= size)
160			return (ENAMETOOLONG);
161	}
162	/* Convert string (IP address or host name) to in_addr_t. */
163	ip = str2ip(iporhost);
164	if (ip == INADDR_NONE)
165		return (EINVAL);
166	sinp->sin_addr.s_addr = ip;
167
168	return (0);
169}
170
171static int
172tcp4_common_setup(const char *addr, void **ctxp, int side)
173{
174	struct tcp4_ctx *tctx;
175	int ret, val;
176
177	tctx = malloc(sizeof(*tctx));
178	if (tctx == NULL)
179		return (errno);
180
181	/* Parse given address. */
182	if ((ret = tcp4_addr(addr, &tctx->tc_sin)) != 0) {
183		free(tctx);
184		return (ret);
185	}
186
187	tctx->tc_fd = socket(AF_INET, SOCK_STREAM, 0);
188	if (tctx->tc_fd == -1) {
189		ret = errno;
190		free(tctx);
191		return (ret);
192	}
193
194	/* Socket settings. */
195	val = 1;
196	if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &val,
197	    sizeof(val)) == -1) {
198		pjdlog_warning("Unable to set TCP_NOELAY on %s", addr);
199	}
200	val = 131072;
201	if (setsockopt(tctx->tc_fd, SOL_SOCKET, SO_SNDBUF, &val,
202	    sizeof(val)) == -1) {
203		pjdlog_warning("Unable to set send buffer size on %s", addr);
204	}
205	val = 131072;
206	if (setsockopt(tctx->tc_fd, SOL_SOCKET, SO_RCVBUF, &val,
207	    sizeof(val)) == -1) {
208		pjdlog_warning("Unable to set receive buffer size on %s", addr);
209	}
210
211	tctx->tc_side = side;
212	tctx->tc_magic = TCP4_CTX_MAGIC;
213	*ctxp = tctx;
214
215	return (0);
216}
217
218static int
219tcp4_client(const char *addr, void **ctxp)
220{
221
222	return (tcp4_common_setup(addr, ctxp, TCP4_SIDE_CLIENT));
223}
224
225static int
226tcp4_connect(void *ctx)
227{
228	struct tcp4_ctx *tctx = ctx;
229	struct timeval tv;
230	fd_set fdset;
231	socklen_t esize;
232	int error, flags, ret;
233
234	assert(tctx != NULL);
235	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
236	assert(tctx->tc_side == TCP4_SIDE_CLIENT);
237	assert(tctx->tc_fd >= 0);
238
239	flags = fcntl(tctx->tc_fd, F_GETFL);
240	if (flags == -1) {
241		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
242		    "fcntl(F_GETFL) failed"));
243		return (errno);
244	}
245	/*
246	 * We make socket non-blocking so we have decided about connection
247	 * timeout.
248	 */
249	flags |= O_NONBLOCK;
250	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
251		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
252		    "fcntl(F_SETFL, O_NONBLOCK) failed"));
253		return (errno);
254	}
255
256	if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
257	    sizeof(tctx->tc_sin)) == 0) {
258		error = 0;
259		goto done;
260	}
261	if (errno != EINPROGRESS) {
262		error = errno;
263		pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed");
264		goto done;
265	}
266	/*
267	 * Connection can't be established immediately, let's wait
268	 * for HAST_TIMEOUT seconds.
269	 */
270	tv.tv_sec = HAST_TIMEOUT;
271	tv.tv_usec = 0;
272again:
273	FD_ZERO(&fdset);
274	FD_SET(tctx->tc_fd, &fdset);
275	ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv);
276	if (ret == 0) {
277		error = ETIMEDOUT;
278		goto done;
279	} else if (ret == -1) {
280		if (errno == EINTR)
281			goto again;
282		error = errno;
283		pjdlog_common(LOG_DEBUG, 1, errno, "select() failed");
284		goto done;
285	}
286	assert(ret > 0);
287	assert(FD_ISSET(tctx->tc_fd, &fdset));
288	esize = sizeof(error);
289	if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error,
290	    &esize) == -1) {
291		error = errno;
292		pjdlog_common(LOG_DEBUG, 1, errno,
293		    "getsockopt(SO_ERROR) failed");
294		goto done;
295	}
296	if (error != 0) {
297		pjdlog_common(LOG_DEBUG, 1, error,
298		    "getsockopt(SO_ERROR) returned error");
299		goto done;
300	}
301	error = 0;
302done:
303	flags &= ~O_NONBLOCK;
304	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
305		if (error == 0)
306			error = errno;
307		pjdlog_common(LOG_DEBUG, 1, errno,
308		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
309	}
310	return (error);
311}
312
313static int
314tcp4_server(const char *addr, void **ctxp)
315{
316	struct tcp4_ctx *tctx;
317	int ret, val;
318
319	ret = tcp4_common_setup(addr, ctxp, TCP4_SIDE_SERVER_LISTEN);
320	if (ret != 0)
321		return (ret);
322
323	tctx = *ctxp;
324
325	val = 1;
326	/* Ignore failure. */
327	(void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val,
328	   sizeof(val));
329
330	if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
331	    sizeof(tctx->tc_sin)) < 0) {
332		ret = errno;
333		tcp4_close(tctx);
334		return (ret);
335	}
336	if (listen(tctx->tc_fd, 8) < 0) {
337		ret = errno;
338		tcp4_close(tctx);
339		return (ret);
340	}
341
342	return (0);
343}
344
345static int
346tcp4_accept(void *ctx, void **newctxp)
347{
348	struct tcp4_ctx *tctx = ctx;
349	struct tcp4_ctx *newtctx;
350	socklen_t fromlen;
351	int ret;
352
353	assert(tctx != NULL);
354	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
355	assert(tctx->tc_side == TCP4_SIDE_SERVER_LISTEN);
356	assert(tctx->tc_fd >= 0);
357
358	newtctx = malloc(sizeof(*newtctx));
359	if (newtctx == NULL)
360		return (errno);
361
362	fromlen = sizeof(tctx->tc_sin);
363	newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
364	    &fromlen);
365	if (newtctx->tc_fd < 0) {
366		ret = errno;
367		free(newtctx);
368		return (ret);
369	}
370
371	newtctx->tc_side = TCP4_SIDE_SERVER_WORK;
372	newtctx->tc_magic = TCP4_CTX_MAGIC;
373	*newctxp = newtctx;
374
375	return (0);
376}
377
378static int
379tcp4_send(void *ctx, const unsigned char *data, size_t size)
380{
381	struct tcp4_ctx *tctx = ctx;
382
383	assert(tctx != NULL);
384	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
385	assert(tctx->tc_fd >= 0);
386
387	return (proto_common_send(tctx->tc_fd, data, size));
388}
389
390static int
391tcp4_recv(void *ctx, unsigned char *data, size_t size)
392{
393	struct tcp4_ctx *tctx = ctx;
394
395	assert(tctx != NULL);
396	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
397	assert(tctx->tc_fd >= 0);
398
399	return (proto_common_recv(tctx->tc_fd, data, size));
400}
401
402static int
403tcp4_descriptor(const void *ctx)
404{
405	const struct tcp4_ctx *tctx = ctx;
406
407	assert(tctx != NULL);
408	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
409
410	return (tctx->tc_fd);
411}
412
413static void
414sin2str(struct sockaddr_in *sinp, char *addr, size_t size)
415{
416	in_addr_t ip;
417	unsigned int port;
418
419	assert(addr != NULL);
420	assert(sinp->sin_family == AF_INET);
421
422	ip = ntohl(sinp->sin_addr.s_addr);
423	port = ntohs(sinp->sin_port);
424	PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%u.%u.%u.%u:%u",
425	    ((ip >> 24) & 0xff), ((ip >> 16) & 0xff), ((ip >> 8) & 0xff),
426	    (ip & 0xff), port) < (ssize_t)size);
427}
428
429static bool
430tcp4_address_match(const void *ctx, const char *addr)
431{
432	const struct tcp4_ctx *tctx = ctx;
433	struct sockaddr_in sin;
434	socklen_t sinlen;
435	in_addr_t ip1, ip2;
436
437	assert(tctx != NULL);
438	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
439
440	if (tcp4_addr(addr, &sin) != 0)
441		return (false);
442	ip1 = sin.sin_addr.s_addr;
443
444	sinlen = sizeof(sin);
445	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0)
446		return (false);
447	ip2 = sin.sin_addr.s_addr;
448
449	return (ip1 == ip2);
450}
451
452static void
453tcp4_local_address(const void *ctx, char *addr, size_t size)
454{
455	const struct tcp4_ctx *tctx = ctx;
456	struct sockaddr_in sin;
457	socklen_t sinlen;
458
459	assert(tctx != NULL);
460	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
461
462	sinlen = sizeof(sin);
463	if (getsockname(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
464		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
465		return;
466	}
467	sin2str(&sin, addr, size);
468}
469
470static void
471tcp4_remote_address(const void *ctx, char *addr, size_t size)
472{
473	const struct tcp4_ctx *tctx = ctx;
474	struct sockaddr_in sin;
475	socklen_t sinlen;
476
477	assert(tctx != NULL);
478	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
479
480	sinlen = sizeof(sin);
481	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
482		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
483		return;
484	}
485	sin2str(&sin, addr, size);
486}
487
488static void
489tcp4_close(void *ctx)
490{
491	struct tcp4_ctx *tctx = ctx;
492
493	assert(tctx != NULL);
494	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
495
496	if (tctx->tc_fd >= 0)
497		close(tctx->tc_fd);
498	tctx->tc_magic = 0;
499	free(tctx);
500}
501
502static struct hast_proto tcp4_proto = {
503	.hp_name = "tcp4",
504	.hp_client = tcp4_client,
505	.hp_connect = tcp4_connect,
506	.hp_server = tcp4_server,
507	.hp_accept = tcp4_accept,
508	.hp_send = tcp4_send,
509	.hp_recv = tcp4_recv,
510	.hp_descriptor = tcp4_descriptor,
511	.hp_address_match = tcp4_address_match,
512	.hp_local_address = tcp4_local_address,
513	.hp_remote_address = tcp4_remote_address,
514	.hp_close = tcp4_close
515};
516
517static __constructor void
518tcp4_ctor(void)
519{
520
521	proto_register(&tcp4_proto, true);
522}
523