]> CyberLeo.Net >> Repos - FreeBSD/releng/8.2.git/blob - sbin/hastd/proto_tcp4.c
MFC r217548:
[FreeBSD/releng/8.2.git] / sbin / hastd / proto_tcp4.c
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$");
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
54 struct 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
64 static void tcp4_close(void *ctx);
65
66 static in_addr_t
67 str2ip(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  */
87 static int
88 numfromstr(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);
109 invalid:
110         errno = EINVAL;
111         return (-1);
112 }
113
114 static int
115 tcp4_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
170 static int
171 tcp4_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
217 static int
218 tcp4_client(const char *addr, void **ctxp)
219 {
220
221         return (tcp4_common_setup(addr, ctxp, TCP4_SIDE_CLIENT));
222 }
223
224 static int
225 tcp4_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;
271 again:
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;
301 done:
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
312 static int
313 tcp4_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
344 static int
345 tcp4_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
377 static int
378 tcp4_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
389 static int
390 tcp4_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
401 static int
402 tcp4_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
412 static void
413 sin2str(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
428 static bool
429 tcp4_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
451 static void
452 tcp4_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
469 static void
470 tcp4_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
487 static void
488 tcp4_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
501 static 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
516 static __constructor void
517 tcp4_ctor(void)
518 {
519
520         proto_register(&tcp4_proto, true);
521 }