proto_tcp.c revision 210876
1327Sjkh/*-
2327Sjkh * Copyright (c) 2009-2010 The FreeBSD Foundation
3327Sjkh * All rights reserved.
4327Sjkh *
5327Sjkh * This software was developed by Pawel Jakub Dawidek under sponsorship from
6327Sjkh * the FreeBSD Foundation.
7327Sjkh *
8327Sjkh * Redistribution and use in source and binary forms, with or without
9327Sjkh * modification, are permitted provided that the following conditions
10327Sjkh * are met:
11327Sjkh * 1. Redistributions of source code must retain the above copyright
12327Sjkh *    notice, this list of conditions and the following disclaimer.
13327Sjkh * 2. Redistributions in binary form must reproduce the above copyright
14327Sjkh *    notice, this list of conditions and the following disclaimer in the
15327Sjkh *    documentation and/or other materials provided with the distribution.
16327Sjkh *
17327Sjkh * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
1831997Shoek * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19327Sjkh * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20327Sjkh * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21327Sjkh * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2293520Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2393520Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2493520Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25327Sjkh * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26327Sjkh * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2774699Ssobomax * SUCH DAMAGE.
28327Sjkh */
29103149Ssobomax
3055567Sphantom#include <sys/cdefs.h>
31327Sjkh__FBSDID("$FreeBSD: head/sbin/hastd/proto_tcp4.c 210876 2010-08-05 18:27:41Z pjd $");
3272174Ssobomax
33411Sjkh#include <sys/param.h>	/* MAXHOSTNAMELEN */
3484745Ssobomax
3511780Sjkh#include <netinet/in.h>
36392Sjkh#include <netinet/tcp.h>
3796030Ssobomax
3874699Ssobomax#include <assert.h>
39327Sjkh#include <errno.h>
4030221Scharnier#include <fcntl.h>
4130221Scharnier#include <netdb.h>
42327Sjkh#include <stdbool.h>
43327Sjkh#include <stdint.h>
44327Sjkh#include <stdio.h>
45327Sjkh#include <string.h>
46327Sjkh#include <unistd.h>
4756001Sdan
48327Sjkh#include "hast.h"
4974699Ssobomax#include "pjdlog.h"
5074699Ssobomax#include "proto_impl.h"
5174699Ssobomax#include "subr.h"
5274699Ssobomax
5374699Ssobomax#define	TCP4_CTX_MAGIC	0x7c441c
54327Sjkhstruct tcp4_ctx {
5546105Sjkh	int			tc_magic;
5672174Ssobomax	struct sockaddr_in	tc_sin;
5746105Sjkh	int			tc_fd;
5846105Sjkh	int			tc_side;
5946105Sjkh#define	TCP4_SIDE_CLIENT	0
60327Sjkh#define	TCP4_SIDE_SERVER_LISTEN	1
61327Sjkh#define	TCP4_SIDE_SERVER_WORK	2
6272174Ssobomax};
63327Sjkh
64327Sjkhstatic void tcp4_close(void *ctx);
65327Sjkh
66327Sjkhstatic in_addr_t
67327Sjkhstr2ip(const char *str)
68327Sjkh{
694996Sjkh	struct hostent *hp;
70327Sjkh	in_addr_t ip;
71327Sjkh
72327Sjkh	ip = inet_addr(str);
73327Sjkh	if (ip != INADDR_NONE) {
74327Sjkh		/* It is a valid IP address. */
75327Sjkh		return (ip);
76327Sjkh	}
77327Sjkh	/* Check if it is a valid host name. */
78327Sjkh	hp = gethostbyname(str);
79327Sjkh	if (hp == NULL)
80327Sjkh		return (INADDR_NONE);
81327Sjkh	return (((struct in_addr *)(void *)hp->h_addr)->s_addr);
82327Sjkh}
83327Sjkh
84327Sjkh/*
85327Sjkh * Function converts the given string to unsigned number.
86327Sjkh */
87327Sjkhstatic int
884996Sjkhnumfromstr(const char *str, intmax_t minnum, intmax_t maxnum, intmax_t *nump)
894996Sjkh{
904996Sjkh	intmax_t digit, num;
914996Sjkh
92327Sjkh	if (str[0] == '\0')
93327Sjkh		goto invalid;	/* Empty string. */
94327Sjkh	num = 0;
95327Sjkh	for (; *str != '\0'; str++) {
9671965Sjkh		if (*str < '0' || *str > '9')
9771965Sjkh			goto invalid;	/* Non-digit character. */
9871965Sjkh		digit = *str - '0';
9971965Sjkh		if (num > num * 10 + digit)
10072174Ssobomax			goto invalid;	/* Overflow. */
10172174Ssobomax		num = num * 10 + digit;
10272174Ssobomax		if (num > maxnum)
10372174Ssobomax			goto invalid;	/* Too big. */
104327Sjkh	}
105327Sjkh	if (num < minnum)
106327Sjkh		goto invalid;	/* Too small. */
107327Sjkh	*nump = num;
108327Sjkh	return (0);
109327Sjkhinvalid:
110327Sjkh	errno = EINVAL;
111327Sjkh	return (-1);
112327Sjkh}
113327Sjkh
114327Sjkhstatic int
115327Sjkhtcp4_addr(const char *addr, struct sockaddr_in *sinp)
1164996Sjkh{
1174996Sjkh	char iporhost[MAXHOSTNAMELEN];
1184996Sjkh	const char *pp;
1194996Sjkh	size_t size;
120411Sjkh	in_addr_t ip;
121411Sjkh
122411Sjkh	if (addr == NULL)
123411Sjkh		return (-1);
1244996Sjkh
1254996Sjkh	if (strncasecmp(addr, "tcp4://", 7) == 0)
1264996Sjkh		addr += 7;
1274996Sjkh	else if (strncasecmp(addr, "tcp://", 6) == 0)
12872174Ssobomax		addr += 6;
12972174Ssobomax	else {
13072174Ssobomax		/*
13162775Ssobomax		 * Because TCP4 is the default assume IP or host is given without
13267454Ssobomax		 * prefix.
13367454Ssobomax		 */
13467454Ssobomax	}
13567454Ssobomax
13696030Ssobomax	sinp->sin_family = AF_INET;
13796030Ssobomax	sinp->sin_len = sizeof(*sinp);
13896030Ssobomax	/* Extract optional port. */
13996030Ssobomax	pp = strrchr(addr, ':');
14096030Ssobomax	if (pp == NULL) {
14196030Ssobomax		/* Port not given, use the default. */
14284750Ssobomax		sinp->sin_port = htons(HASTD_PORT);
14384750Ssobomax	} else {
14484750Ssobomax		intmax_t port;
14584750Ssobomax
146379Sjkh		if (numfromstr(pp + 1, 1, 65535, &port) < 0)
147379Sjkh			return (errno);
148379Sjkh		sinp->sin_port = htons(port);
149379Sjkh	}
150411Sjkh	/* Extract host name or IP address. */
151411Sjkh	if (pp == NULL) {
152411Sjkh		size = sizeof(iporhost);
153411Sjkh		if (strlcpy(iporhost, addr, size) >= size)
154383Sjkh			return (ENAMETOOLONG);
15585019Ssobomax	} else {
156383Sjkh		size = (size_t)(pp - addr + 1);
157383Sjkh		if (size > sizeof(iporhost))
15872174Ssobomax			return (ENAMETOOLONG);
15972174Ssobomax		if (strlcpy(iporhost, addr, size) >= size)
16072174Ssobomax			return (ENAMETOOLONG);
16172174Ssobomax	}
162392Sjkh	/* Convert string (IP address or host name) to in_addr_t. */
163392Sjkh	ip = str2ip(iporhost);
164392Sjkh	if (ip == INADDR_NONE)
165392Sjkh		return (EINVAL);
16674699Ssobomax	sinp->sin_addr.s_addr = ip;
16774699Ssobomax
16874699Ssobomax	return (0);
16974699Ssobomax}
17074699Ssobomax
17174699Ssobomaxstatic int
17274699Ssobomaxtcp4_common_setup(const char *addr, void **ctxp, int side)
17374699Ssobomax{
17474699Ssobomax	struct tcp4_ctx *tctx;
17574699Ssobomax	int ret, val;
17674699Ssobomax
17774699Ssobomax	tctx = malloc(sizeof(*tctx));
17874699Ssobomax	if (tctx == NULL)
17974699Ssobomax		return (errno);
180103149Ssobomax
181103149Ssobomax	/* Parse given address. */
182103149Ssobomax	if ((ret = tcp4_addr(addr, &tctx->tc_sin)) != 0) {
183103149Ssobomax		free(tctx);
184327Sjkh		return (ret);
185327Sjkh	}
186327Sjkh
18730221Scharnier	tctx->tc_fd = socket(AF_INET, SOCK_STREAM, 0);
188327Sjkh	if (tctx->tc_fd == -1) {
189327Sjkh		ret = errno;
19046105Sjkh		free(tctx);
191327Sjkh		return (ret);
1928857Srgrimes	}
193327Sjkh
194327Sjkh	/* Socket settings. */
195103149Ssobomax	val = 1;
196103149Ssobomax	if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &val,
197103149Ssobomax	    sizeof(val)) == -1) {
198103149Ssobomax		pjdlog_warning("Unable to set TCP_NOELAY on %s", addr);
199103149Ssobomax	}
200103149Ssobomax	val = 131072;
201103149Ssobomax	if (setsockopt(tctx->tc_fd, SOL_SOCKET, SO_SNDBUF, &val,
202327Sjkh	    sizeof(val)) == -1) {
203327Sjkh		pjdlog_warning("Unable to set send buffer size on %s", addr);
2044996Sjkh	}
205327Sjkh	val = 131072;
206327Sjkh	if (setsockopt(tctx->tc_fd, SOL_SOCKET, SO_RCVBUF, &val,
20760563Ssteve	    sizeof(val)) == -1) {
20895934Ssobomax		pjdlog_warning("Unable to set receive buffer size on %s", addr);
20995934Ssobomax	}
21095934Ssobomax
21195934Ssobomax	tctx->tc_side = side;
21295934Ssobomax	tctx->tc_magic = TCP4_CTX_MAGIC;
21372174Ssobomax	*ctxp = tctx;
21472174Ssobomax
21572174Ssobomax	return (0);
21672174Ssobomax}
21772174Ssobomax
21872174Ssobomaxstatic int
21972174Ssobomaxtcp4_client(const char *addr, void **ctxp)
22072174Ssobomax{
22172174Ssobomax
22272174Ssobomax	return (tcp4_common_setup(addr, ctxp, TCP4_SIDE_CLIENT));
22372174Ssobomax}
22472174Ssobomax
22572174Ssobomaxstatic int
22660563Sstevetcp4_connect(void *ctx)
22760563Ssteve{
22856001Sdan	struct tcp4_ctx *tctx = ctx;
229327Sjkh	struct timeval tv;
230327Sjkh	fd_set fdset;
23174699Ssobomax	socklen_t esize;
23296030Ssobomax	int error, flags, ret;
23330221Scharnier
234327Sjkh	assert(tctx != NULL);
235327Sjkh	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
236327Sjkh	assert(tctx->tc_side == TCP4_SIDE_CLIENT);
237327Sjkh	assert(tctx->tc_fd >= 0);
23830221Scharnier
23930221Scharnier	flags = fcntl(tctx->tc_fd, F_GETFL);
240327Sjkh	if (flags == -1) {
24196067Ssobomax		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
24284750Ssobomax		    "fcntl(F_GETFL) failed"));
24396067Ssobomax		return (errno);
24496067Ssobomax	}
24596067Ssobomax	/*
24630221Scharnier	 * We make socket non-blocking so we have decided about connection
247327Sjkh	 * timeout.
248327Sjkh	 */
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