proto_tcp.c revision 218158
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 218158 2011-02-01 07:58:43Z 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 void tcp4_close(void *ctx);
64
65static in_addr_t
66str2ip(const char *str)
67{
68	struct hostent *hp;
69	in_addr_t ip;
70
71	ip = inet_addr(str);
72	if (ip != INADDR_NONE) {
73		/* It is a valid IP address. */
74		return (ip);
75	}
76	/* Check if it is a valid host name. */
77	hp = gethostbyname(str);
78	if (hp == NULL)
79		return (INADDR_NONE);
80	return (((struct in_addr *)(void *)hp->h_addr)->s_addr);
81}
82
83/*
84 * Function converts the given string to unsigned number.
85 */
86static int
87numfromstr(const char *str, intmax_t minnum, intmax_t maxnum, intmax_t *nump)
88{
89	intmax_t digit, num;
90
91	if (str[0] == '\0')
92		goto invalid;	/* Empty string. */
93	num = 0;
94	for (; *str != '\0'; str++) {
95		if (*str < '0' || *str > '9')
96			goto invalid;	/* Non-digit character. */
97		digit = *str - '0';
98		if (num > num * 10 + digit)
99			goto invalid;	/* Overflow. */
100		num = num * 10 + digit;
101		if (num > maxnum)
102			goto invalid;	/* Too big. */
103	}
104	if (num < minnum)
105		goto invalid;	/* Too small. */
106	*nump = num;
107	return (0);
108invalid:
109	errno = EINVAL;
110	return (-1);
111}
112
113static int
114tcp4_addr(const char *addr, struct sockaddr_in *sinp)
115{
116	char iporhost[MAXHOSTNAMELEN];
117	const char *pp;
118	size_t size;
119	in_addr_t ip;
120
121	if (addr == NULL)
122		return (-1);
123
124	if (strncasecmp(addr, "tcp4://", 7) == 0)
125		addr += 7;
126	else if (strncasecmp(addr, "tcp://", 6) == 0)
127		addr += 6;
128	else {
129		/*
130		 * Because TCP4 is the default assume IP or host is given without
131		 * prefix.
132		 */
133	}
134
135	sinp->sin_family = AF_INET;
136	sinp->sin_len = sizeof(*sinp);
137	/* Extract optional port. */
138	pp = strrchr(addr, ':');
139	if (pp == NULL) {
140		/* Port not given, use the default. */
141		sinp->sin_port = htons(HASTD_PORT);
142	} else {
143		intmax_t port;
144
145		if (numfromstr(pp + 1, 1, 65535, &port) < 0)
146			return (errno);
147		sinp->sin_port = htons(port);
148	}
149	/* Extract host name or IP address. */
150	if (pp == NULL) {
151		size = sizeof(iporhost);
152		if (strlcpy(iporhost, addr, size) >= size)
153			return (ENAMETOOLONG);
154	} else {
155		size = (size_t)(pp - addr + 1);
156		if (size > sizeof(iporhost))
157			return (ENAMETOOLONG);
158		(void)strlcpy(iporhost, addr, size);
159	}
160	/* Convert string (IP address or host name) to in_addr_t. */
161	ip = str2ip(iporhost);
162	if (ip == INADDR_NONE)
163		return (EINVAL);
164	sinp->sin_addr.s_addr = ip;
165
166	return (0);
167}
168
169static int
170tcp4_common_setup(const char *addr, void **ctxp, int side)
171{
172	struct tcp4_ctx *tctx;
173	int ret, nodelay;
174
175	tctx = malloc(sizeof(*tctx));
176	if (tctx == NULL)
177		return (errno);
178
179	/* Parse given address. */
180	if ((ret = tcp4_addr(addr, &tctx->tc_sin)) != 0) {
181		free(tctx);
182		return (ret);
183	}
184
185	tctx->tc_fd = socket(AF_INET, SOCK_STREAM, 0);
186	if (tctx->tc_fd == -1) {
187		ret = errno;
188		free(tctx);
189		return (ret);
190	}
191
192	/* Socket settings. */
193	nodelay = 1;
194	if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
195	    sizeof(nodelay)) == -1) {
196		pjdlog_warning("Unable to set TCP_NOELAY on %s", addr);
197	}
198
199	tctx->tc_side = side;
200	tctx->tc_magic = TCP4_CTX_MAGIC;
201	*ctxp = tctx;
202
203	return (0);
204}
205
206static int
207tcp4_client(const char *addr, void **ctxp)
208{
209
210	return (tcp4_common_setup(addr, ctxp, TCP4_SIDE_CLIENT));
211}
212
213static int
214tcp4_connect(void *ctx)
215{
216	struct tcp4_ctx *tctx = ctx;
217	struct timeval tv;
218	fd_set fdset;
219	socklen_t esize;
220	int error, flags, ret;
221
222	PJDLOG_ASSERT(tctx != NULL);
223	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
224	PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_CLIENT);
225	PJDLOG_ASSERT(tctx->tc_fd >= 0);
226
227	flags = fcntl(tctx->tc_fd, F_GETFL);
228	if (flags == -1) {
229		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
230		    "fcntl(F_GETFL) failed"));
231		return (errno);
232	}
233	/*
234	 * We make socket non-blocking so we can handle connection timeout
235	 * manually.
236	 */
237	flags |= O_NONBLOCK;
238	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
239		KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno,
240		    "fcntl(F_SETFL, O_NONBLOCK) failed"));
241		return (errno);
242	}
243
244	if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
245	    sizeof(tctx->tc_sin)) == 0) {
246		error = 0;
247		goto done;
248	}
249	if (errno != EINPROGRESS) {
250		error = errno;
251		pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed");
252		goto done;
253	}
254	/*
255	 * Connection can't be established immediately, let's wait
256	 * for HAST_TIMEOUT seconds.
257	 */
258	tv.tv_sec = HAST_TIMEOUT;
259	tv.tv_usec = 0;
260again:
261	FD_ZERO(&fdset);
262	FD_SET(tctx->tc_fd, &fdset);
263	ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv);
264	if (ret == 0) {
265		error = ETIMEDOUT;
266		goto done;
267	} else if (ret == -1) {
268		if (errno == EINTR)
269			goto again;
270		error = errno;
271		pjdlog_common(LOG_DEBUG, 1, errno, "select() failed");
272		goto done;
273	}
274	PJDLOG_ASSERT(ret > 0);
275	PJDLOG_ASSERT(FD_ISSET(tctx->tc_fd, &fdset));
276	esize = sizeof(error);
277	if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error,
278	    &esize) == -1) {
279		error = errno;
280		pjdlog_common(LOG_DEBUG, 1, errno,
281		    "getsockopt(SO_ERROR) failed");
282		goto done;
283	}
284	if (error != 0) {
285		pjdlog_common(LOG_DEBUG, 1, error,
286		    "getsockopt(SO_ERROR) returned error");
287		goto done;
288	}
289	error = 0;
290done:
291	flags &= ~O_NONBLOCK;
292	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
293		if (error == 0)
294			error = errno;
295		pjdlog_common(LOG_DEBUG, 1, errno,
296		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
297	}
298	return (error);
299}
300
301static int
302tcp4_server(const char *addr, void **ctxp)
303{
304	struct tcp4_ctx *tctx;
305	int ret, val;
306
307	ret = tcp4_common_setup(addr, ctxp, TCP4_SIDE_SERVER_LISTEN);
308	if (ret != 0)
309		return (ret);
310
311	tctx = *ctxp;
312
313	val = 1;
314	/* Ignore failure. */
315	(void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val,
316	   sizeof(val));
317
318	if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
319	    sizeof(tctx->tc_sin)) < 0) {
320		ret = errno;
321		tcp4_close(tctx);
322		return (ret);
323	}
324	if (listen(tctx->tc_fd, 8) < 0) {
325		ret = errno;
326		tcp4_close(tctx);
327		return (ret);
328	}
329
330	return (0);
331}
332
333static int
334tcp4_accept(void *ctx, void **newctxp)
335{
336	struct tcp4_ctx *tctx = ctx;
337	struct tcp4_ctx *newtctx;
338	socklen_t fromlen;
339	int ret;
340
341	PJDLOG_ASSERT(tctx != NULL);
342	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
343	PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_SERVER_LISTEN);
344	PJDLOG_ASSERT(tctx->tc_fd >= 0);
345
346	newtctx = malloc(sizeof(*newtctx));
347	if (newtctx == NULL)
348		return (errno);
349
350	fromlen = sizeof(tctx->tc_sin);
351	newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin,
352	    &fromlen);
353	if (newtctx->tc_fd < 0) {
354		ret = errno;
355		free(newtctx);
356		return (ret);
357	}
358
359	newtctx->tc_side = TCP4_SIDE_SERVER_WORK;
360	newtctx->tc_magic = TCP4_CTX_MAGIC;
361	*newctxp = newtctx;
362
363	return (0);
364}
365
366static int
367tcp4_send(void *ctx, const unsigned char *data, size_t size)
368{
369	struct tcp4_ctx *tctx = ctx;
370
371	PJDLOG_ASSERT(tctx != NULL);
372	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
373	PJDLOG_ASSERT(tctx->tc_fd >= 0);
374
375	return (proto_common_send(tctx->tc_fd, data, size));
376}
377
378static int
379tcp4_recv(void *ctx, unsigned char *data, size_t size)
380{
381	struct tcp4_ctx *tctx = ctx;
382
383	PJDLOG_ASSERT(tctx != NULL);
384	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
385	PJDLOG_ASSERT(tctx->tc_fd >= 0);
386
387	return (proto_common_recv(tctx->tc_fd, data, size));
388}
389
390static int
391tcp4_descriptor(const void *ctx)
392{
393	const struct tcp4_ctx *tctx = ctx;
394
395	PJDLOG_ASSERT(tctx != NULL);
396	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
397
398	return (tctx->tc_fd);
399}
400
401static void
402sin2str(struct sockaddr_in *sinp, char *addr, size_t size)
403{
404	in_addr_t ip;
405	unsigned int port;
406
407	PJDLOG_ASSERT(addr != NULL);
408	PJDLOG_ASSERT(sinp->sin_family == AF_INET);
409
410	ip = ntohl(sinp->sin_addr.s_addr);
411	port = ntohs(sinp->sin_port);
412	PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%u.%u.%u.%u:%u",
413	    ((ip >> 24) & 0xff), ((ip >> 16) & 0xff), ((ip >> 8) & 0xff),
414	    (ip & 0xff), port) < (ssize_t)size);
415}
416
417static bool
418tcp4_address_match(const void *ctx, const char *addr)
419{
420	const struct tcp4_ctx *tctx = ctx;
421	struct sockaddr_in sin;
422	socklen_t sinlen;
423	in_addr_t ip1, ip2;
424
425	PJDLOG_ASSERT(tctx != NULL);
426	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
427
428	if (tcp4_addr(addr, &sin) != 0)
429		return (false);
430	ip1 = sin.sin_addr.s_addr;
431
432	sinlen = sizeof(sin);
433	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0)
434		return (false);
435	ip2 = sin.sin_addr.s_addr;
436
437	return (ip1 == ip2);
438}
439
440static void
441tcp4_local_address(const void *ctx, char *addr, size_t size)
442{
443	const struct tcp4_ctx *tctx = ctx;
444	struct sockaddr_in sin;
445	socklen_t sinlen;
446
447	PJDLOG_ASSERT(tctx != NULL);
448	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
449
450	sinlen = sizeof(sin);
451	if (getsockname(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
452		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
453		return;
454	}
455	sin2str(&sin, addr, size);
456}
457
458static void
459tcp4_remote_address(const void *ctx, char *addr, size_t size)
460{
461	const struct tcp4_ctx *tctx = ctx;
462	struct sockaddr_in sin;
463	socklen_t sinlen;
464
465	PJDLOG_ASSERT(tctx != NULL);
466	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
467
468	sinlen = sizeof(sin);
469	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) {
470		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
471		return;
472	}
473	sin2str(&sin, addr, size);
474}
475
476static void
477tcp4_close(void *ctx)
478{
479	struct tcp4_ctx *tctx = ctx;
480
481	PJDLOG_ASSERT(tctx != NULL);
482	PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC);
483
484	if (tctx->tc_fd >= 0)
485		close(tctx->tc_fd);
486	tctx->tc_magic = 0;
487	free(tctx);
488}
489
490static struct hast_proto tcp4_proto = {
491	.hp_name = "tcp4",
492	.hp_client = tcp4_client,
493	.hp_connect = tcp4_connect,
494	.hp_server = tcp4_server,
495	.hp_accept = tcp4_accept,
496	.hp_send = tcp4_send,
497	.hp_recv = tcp4_recv,
498	.hp_descriptor = tcp4_descriptor,
499	.hp_address_match = tcp4_address_match,
500	.hp_local_address = tcp4_local_address,
501	.hp_remote_address = tcp4_remote_address,
502	.hp_close = tcp4_close
503};
504
505static __constructor void
506tcp4_ctor(void)
507{
508
509	proto_register(&tcp4_proto, true);
510}
511