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