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