proto_tcp.c revision 211875
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 211875 2010-08-27 13:54:17Z 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		(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_common_setup(const char *addr, void **ctxp, int side)
172{
173	struct tcp4_ctx *tctx;
174	int ret, val;
175
176	tctx = malloc(sizeof(*tctx));
177	if (tctx == NULL)
178		return (errno);
179
180	/* Parse given address. */
181	if ((ret = tcp4_addr(addr, &tctx->tc_sin)) != 0) {
182		free(tctx);
183		return (ret);
184	}
185
186	tctx->tc_fd = socket(AF_INET, SOCK_STREAM, 0);
187	if (tctx->tc_fd == -1) {
188		ret = errno;
189		free(tctx);
190		return (ret);
191	}
192
193	/* Socket settings. */
194	val = 1;
195	if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &val,
196	    sizeof(val)) == -1) {
197		pjdlog_warning("Unable to set TCP_NOELAY on %s", addr);
198	}
199	val = 131072;
200	if (setsockopt(tctx->tc_fd, SOL_SOCKET, SO_SNDBUF, &val,
201	    sizeof(val)) == -1) {
202		pjdlog_warning("Unable to set send buffer size on %s", addr);
203	}
204	val = 131072;
205	if (setsockopt(tctx->tc_fd, SOL_SOCKET, SO_RCVBUF, &val,
206	    sizeof(val)) == -1) {
207		pjdlog_warning("Unable to set receive buffer size on %s", addr);
208	}
209
210	tctx->tc_side = side;
211	tctx->tc_magic = TCP4_CTX_MAGIC;
212	*ctxp = tctx;
213
214	return (0);
215}
216
217static int
218tcp4_client(const char *addr, void **ctxp)
219{
220
221	return (tcp4_common_setup(addr, ctxp, TCP4_SIDE_CLIENT));
222}
223
224static int
225tcp4_connect(void *ctx)
226{
227	struct tcp4_ctx *tctx = ctx;
228	struct timeval tv;
229	fd_set fdset;
230	socklen_t esize;
231	int error, flags, ret;
232
233	assert(tctx != NULL);
234	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
235	assert(tctx->tc_side == TCP4_SIDE_CLIENT);
236	assert(tctx->tc_fd >= 0);
237
238	flags = fcntl(tctx->tc_fd, F_GETFL);
239	if (flags == -1) {
240		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
241		    "fcntl(F_GETFL) failed"));
242		return (errno);
243	}
244	/*
245	 * We make socket non-blocking so we can handle connection timeout
246	 * manually.
247	 */
248	flags |= O_NONBLOCK;
249	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
250		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
251		    "fcntl(F_SETFL, O_NONBLOCK) failed"));
252		return (errno);
253	}
254
255	if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
256	    sizeof(tctx->tc_sin)) == 0) {
257		error = 0;
258		goto done;
259	}
260	if (errno != EINPROGRESS) {
261		error = errno;
262		pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed");
263		goto done;
264	}
265	/*
266	 * Connection can't be established immediately, let's wait
267	 * for HAST_TIMEOUT seconds.
268	 */
269	tv.tv_sec = HAST_TIMEOUT;
270	tv.tv_usec = 0;
271again:
272	FD_ZERO(&fdset);
273	FD_SET(tctx->tc_fd, &fdset);
274	ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv);
275	if (ret == 0) {
276		error = ETIMEDOUT;
277		goto done;
278	} else if (ret == -1) {
279		if (errno == EINTR)
280			goto again;
281		error = errno;
282		pjdlog_common(LOG_DEBUG, 1, errno, "select() failed");
283		goto done;
284	}
285	assert(ret > 0);
286	assert(FD_ISSET(tctx->tc_fd, &fdset));
287	esize = sizeof(error);
288	if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error,
289	    &esize) == -1) {
290		error = errno;
291		pjdlog_common(LOG_DEBUG, 1, errno,
292		    "getsockopt(SO_ERROR) failed");
293		goto done;
294	}
295	if (error != 0) {
296		pjdlog_common(LOG_DEBUG, 1, error,
297		    "getsockopt(SO_ERROR) returned error");
298		goto done;
299	}
300	error = 0;
301done:
302	flags &= ~O_NONBLOCK;
303	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
304		if (error == 0)
305			error = errno;
306		pjdlog_common(LOG_DEBUG, 1, errno,
307		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
308	}
309	return (error);
310}
311
312static int
313tcp4_server(const char *addr, void **ctxp)
314{
315	struct tcp4_ctx *tctx;
316	int ret, val;
317
318	ret = tcp4_common_setup(addr, ctxp, TCP4_SIDE_SERVER_LISTEN);
319	if (ret != 0)
320		return (ret);
321
322	tctx = *ctxp;
323
324	val = 1;
325	/* Ignore failure. */
326	(void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val,
327	   sizeof(val));
328
329	if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
330	    sizeof(tctx->tc_sin)) < 0) {
331		ret = errno;
332		tcp4_close(tctx);
333		return (ret);
334	}
335	if (listen(tctx->tc_fd, 8) < 0) {
336		ret = errno;
337		tcp4_close(tctx);
338		return (ret);
339	}
340
341	return (0);
342}
343
344static int
345tcp4_accept(void *ctx, void **newctxp)
346{
347	struct tcp4_ctx *tctx = ctx;
348	struct tcp4_ctx *newtctx;
349	socklen_t fromlen;
350	int ret;
351
352	assert(tctx != NULL);
353	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
354	assert(tctx->tc_side == TCP4_SIDE_SERVER_LISTEN);
355	assert(tctx->tc_fd >= 0);
356
357	newtctx = malloc(sizeof(*newtctx));
358	if (newtctx == NULL)
359		return (errno);
360
361	fromlen = sizeof(tctx->tc_sin);
362	newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
363	    &fromlen);
364	if (newtctx->tc_fd < 0) {
365		ret = errno;
366		free(newtctx);
367		return (ret);
368	}
369
370	newtctx->tc_side = TCP4_SIDE_SERVER_WORK;
371	newtctx->tc_magic = TCP4_CTX_MAGIC;
372	*newctxp = newtctx;
373
374	return (0);
375}
376
377static int
378tcp4_send(void *ctx, const unsigned char *data, size_t size)
379{
380	struct tcp4_ctx *tctx = ctx;
381
382	assert(tctx != NULL);
383	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
384	assert(tctx->tc_fd >= 0);
385
386	return (proto_common_send(tctx->tc_fd, data, size));
387}
388
389static int
390tcp4_recv(void *ctx, unsigned char *data, size_t size)
391{
392	struct tcp4_ctx *tctx = ctx;
393
394	assert(tctx != NULL);
395	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
396	assert(tctx->tc_fd >= 0);
397
398	return (proto_common_recv(tctx->tc_fd, data, size));
399}
400
401static int
402tcp4_descriptor(const void *ctx)
403{
404	const struct tcp4_ctx *tctx = ctx;
405
406	assert(tctx != NULL);
407	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
408
409	return (tctx->tc_fd);
410}
411
412static void
413sin2str(struct sockaddr_in *sinp, char *addr, size_t size)
414{
415	in_addr_t ip;
416	unsigned int port;
417
418	assert(addr != NULL);
419	assert(sinp->sin_family == AF_INET);
420
421	ip = ntohl(sinp->sin_addr.s_addr);
422	port = ntohs(sinp->sin_port);
423	PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%u.%u.%u.%u:%u",
424	    ((ip >> 24) & 0xff), ((ip >> 16) & 0xff), ((ip >> 8) & 0xff),
425	    (ip & 0xff), port) < (ssize_t)size);
426}
427
428static bool
429tcp4_address_match(const void *ctx, const char *addr)
430{
431	const struct tcp4_ctx *tctx = ctx;
432	struct sockaddr_in sin;
433	socklen_t sinlen;
434	in_addr_t ip1, ip2;
435
436	assert(tctx != NULL);
437	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
438
439	if (tcp4_addr(addr, &sin) != 0)
440		return (false);
441	ip1 = sin.sin_addr.s_addr;
442
443	sinlen = sizeof(sin);
444	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0)
445		return (false);
446	ip2 = sin.sin_addr.s_addr;
447
448	return (ip1 == ip2);
449}
450
451static void
452tcp4_local_address(const void *ctx, char *addr, size_t size)
453{
454	const struct tcp4_ctx *tctx = ctx;
455	struct sockaddr_in sin;
456	socklen_t sinlen;
457
458	assert(tctx != NULL);
459	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
460
461	sinlen = sizeof(sin);
462	if (getsockname(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
463		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
464		return;
465	}
466	sin2str(&sin, addr, size);
467}
468
469static void
470tcp4_remote_address(const void *ctx, char *addr, size_t size)
471{
472	const struct tcp4_ctx *tctx = ctx;
473	struct sockaddr_in sin;
474	socklen_t sinlen;
475
476	assert(tctx != NULL);
477	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
478
479	sinlen = sizeof(sin);
480	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
481		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
482		return;
483	}
484	sin2str(&sin, addr, size);
485}
486
487static void
488tcp4_close(void *ctx)
489{
490	struct tcp4_ctx *tctx = ctx;
491
492	assert(tctx != NULL);
493	assert(tctx->tc_magic == TCP4_CTX_MAGIC);
494
495	if (tctx->tc_fd >= 0)
496		close(tctx->tc_fd);
497	tctx->tc_magic = 0;
498	free(tctx);
499}
500
501static struct hast_proto tcp4_proto = {
502	.hp_name = "tcp4",
503	.hp_client = tcp4_client,
504	.hp_connect = tcp4_connect,
505	.hp_server = tcp4_server,
506	.hp_accept = tcp4_accept,
507	.hp_send = tcp4_send,
508	.hp_recv = tcp4_recv,
509	.hp_descriptor = tcp4_descriptor,
510	.hp_address_match = tcp4_address_match,
511	.hp_local_address = tcp4_local_address,
512	.hp_remote_address = tcp4_remote_address,
513	.hp_close = tcp4_close
514};
515
516static __constructor void
517tcp4_ctor(void)
518{
519
520	proto_register(&tcp4_proto, true);
521}
522