proto_tcp.c revision 219818
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 219818 2011-03-21 08:54:59Z 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 int tcp4_connect_wait(void *ctx, int timeout); 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, int defport, 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(defport); 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_setup_new(const char *addr, int side, void **ctxp) 172{ 173 struct tcp4_ctx *tctx; 174 int ret, nodelay; 175 176 PJDLOG_ASSERT(addr != NULL); 177 PJDLOG_ASSERT(side == TCP4_SIDE_CLIENT || 178 side == TCP4_SIDE_SERVER_LISTEN); 179 PJDLOG_ASSERT(ctxp != NULL); 180 181 tctx = malloc(sizeof(*tctx)); 182 if (tctx == NULL) 183 return (errno); 184 185 /* Parse given address. */ 186 if ((ret = tcp4_addr(addr, HASTD_PORT, &tctx->tc_sin)) != 0) { 187 free(tctx); 188 return (ret); 189 } 190 191 PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC); 192 193 tctx->tc_fd = socket(AF_INET, SOCK_STREAM, 0); 194 if (tctx->tc_fd == -1) { 195 ret = errno; 196 free(tctx); 197 return (ret); 198 } 199 200 PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC); 201 202 /* Socket settings. */ 203 nodelay = 1; 204 if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, 205 sizeof(nodelay)) == -1) { 206 pjdlog_errno(LOG_WARNING, "Unable to set TCP_NOELAY"); 207 } 208 209 tctx->tc_side = side; 210 tctx->tc_magic = TCP4_CTX_MAGIC; 211 *ctxp = tctx; 212 213 return (0); 214} 215 216static int 217tcp4_setup_wrap(int fd, int side, void **ctxp) 218{ 219 struct tcp4_ctx *tctx; 220 221 PJDLOG_ASSERT(fd >= 0); 222 PJDLOG_ASSERT(side == TCP4_SIDE_CLIENT || 223 side == TCP4_SIDE_SERVER_WORK); 224 PJDLOG_ASSERT(ctxp != NULL); 225 226 tctx = malloc(sizeof(*tctx)); 227 if (tctx == NULL) 228 return (errno); 229 230 tctx->tc_fd = fd; 231 tctx->tc_sin.sin_family = AF_UNSPEC; 232 tctx->tc_side = side; 233 tctx->tc_magic = TCP4_CTX_MAGIC; 234 *ctxp = tctx; 235 236 return (0); 237} 238 239static int 240tcp4_client(const char *srcaddr, const char *dstaddr, void **ctxp) 241{ 242 struct tcp4_ctx *tctx; 243 struct sockaddr_in sin; 244 int ret; 245 246 ret = tcp4_setup_new(dstaddr, TCP4_SIDE_CLIENT, ctxp); 247 if (ret != 0) 248 return (ret); 249 tctx = *ctxp; 250 if (srcaddr == NULL) 251 return (0); 252 ret = tcp4_addr(srcaddr, 0, &sin); 253 if (ret != 0) { 254 tcp4_close(tctx); 255 return (ret); 256 } 257 if (bind(tctx->tc_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { 258 ret = errno; 259 tcp4_close(tctx); 260 return (ret); 261 } 262 return (0); 263} 264 265static int 266tcp4_connect(void *ctx, int timeout) 267{ 268 struct tcp4_ctx *tctx = ctx; 269 int error, flags; 270 271 PJDLOG_ASSERT(tctx != NULL); 272 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 273 PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_CLIENT); 274 PJDLOG_ASSERT(tctx->tc_fd >= 0); 275 PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC); 276 PJDLOG_ASSERT(timeout >= -1); 277 278 flags = fcntl(tctx->tc_fd, F_GETFL); 279 if (flags == -1) { 280 KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno, 281 "fcntl(F_GETFL) failed")); 282 return (errno); 283 } 284 /* 285 * We make socket non-blocking so we can handle connection timeout 286 * manually. 287 */ 288 flags |= O_NONBLOCK; 289 if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) { 290 KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno, 291 "fcntl(F_SETFL, O_NONBLOCK) failed")); 292 return (errno); 293 } 294 295 if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 296 sizeof(tctx->tc_sin)) == 0) { 297 if (timeout == -1) 298 return (0); 299 error = 0; 300 goto done; 301 } 302 if (errno != EINPROGRESS) { 303 error = errno; 304 pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed"); 305 goto done; 306 } 307 if (timeout == -1) 308 return (0); 309 return (tcp4_connect_wait(ctx, timeout)); 310done: 311 flags &= ~O_NONBLOCK; 312 if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) { 313 if (error == 0) 314 error = errno; 315 pjdlog_common(LOG_DEBUG, 1, errno, 316 "fcntl(F_SETFL, ~O_NONBLOCK) failed"); 317 } 318 return (error); 319} 320 321static int 322tcp4_connect_wait(void *ctx, int timeout) 323{ 324 struct tcp4_ctx *tctx = ctx; 325 struct timeval tv; 326 fd_set fdset; 327 socklen_t esize; 328 int error, flags, ret; 329 330 PJDLOG_ASSERT(tctx != NULL); 331 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 332 PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_CLIENT); 333 PJDLOG_ASSERT(tctx->tc_fd >= 0); 334 PJDLOG_ASSERT(timeout >= 0); 335 336 tv.tv_sec = timeout; 337 tv.tv_usec = 0; 338again: 339 FD_ZERO(&fdset); 340 FD_SET(tctx->tc_fd, &fdset); 341 ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv); 342 if (ret == 0) { 343 error = ETIMEDOUT; 344 goto done; 345 } else if (ret == -1) { 346 if (errno == EINTR) 347 goto again; 348 error = errno; 349 pjdlog_common(LOG_DEBUG, 1, errno, "select() failed"); 350 goto done; 351 } 352 PJDLOG_ASSERT(ret > 0); 353 PJDLOG_ASSERT(FD_ISSET(tctx->tc_fd, &fdset)); 354 esize = sizeof(error); 355 if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error, 356 &esize) == -1) { 357 error = errno; 358 pjdlog_common(LOG_DEBUG, 1, errno, 359 "getsockopt(SO_ERROR) failed"); 360 goto done; 361 } 362 if (error != 0) { 363 pjdlog_common(LOG_DEBUG, 1, error, 364 "getsockopt(SO_ERROR) returned error"); 365 goto done; 366 } 367 error = 0; 368done: 369 flags = fcntl(tctx->tc_fd, F_GETFL); 370 if (flags == -1) { 371 if (error == 0) 372 error = errno; 373 pjdlog_common(LOG_DEBUG, 1, errno, "fcntl(F_GETFL) failed"); 374 return (error); 375 } 376 flags &= ~O_NONBLOCK; 377 if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) { 378 if (error == 0) 379 error = errno; 380 pjdlog_common(LOG_DEBUG, 1, errno, 381 "fcntl(F_SETFL, ~O_NONBLOCK) failed"); 382 } 383 return (error); 384} 385 386static int 387tcp4_server(const char *addr, void **ctxp) 388{ 389 struct tcp4_ctx *tctx; 390 int ret, val; 391 392 ret = tcp4_setup_new(addr, TCP4_SIDE_SERVER_LISTEN, ctxp); 393 if (ret != 0) 394 return (ret); 395 396 tctx = *ctxp; 397 398 val = 1; 399 /* Ignore failure. */ 400 (void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val, 401 sizeof(val)); 402 403 PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC); 404 405 if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 406 sizeof(tctx->tc_sin)) < 0) { 407 ret = errno; 408 tcp4_close(tctx); 409 return (ret); 410 } 411 if (listen(tctx->tc_fd, 8) < 0) { 412 ret = errno; 413 tcp4_close(tctx); 414 return (ret); 415 } 416 417 return (0); 418} 419 420static int 421tcp4_accept(void *ctx, void **newctxp) 422{ 423 struct tcp4_ctx *tctx = ctx; 424 struct tcp4_ctx *newtctx; 425 socklen_t fromlen; 426 int ret; 427 428 PJDLOG_ASSERT(tctx != NULL); 429 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 430 PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_SERVER_LISTEN); 431 PJDLOG_ASSERT(tctx->tc_fd >= 0); 432 PJDLOG_ASSERT(tctx->tc_sin.sin_family != AF_UNSPEC); 433 434 newtctx = malloc(sizeof(*newtctx)); 435 if (newtctx == NULL) 436 return (errno); 437 438 fromlen = sizeof(tctx->tc_sin); 439 newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 440 &fromlen); 441 if (newtctx->tc_fd < 0) { 442 ret = errno; 443 free(newtctx); 444 return (ret); 445 } 446 447 newtctx->tc_side = TCP4_SIDE_SERVER_WORK; 448 newtctx->tc_magic = TCP4_CTX_MAGIC; 449 *newctxp = newtctx; 450 451 return (0); 452} 453 454static int 455tcp4_wrap(int fd, bool client, void **ctxp) 456{ 457 458 return (tcp4_setup_wrap(fd, 459 client ? TCP4_SIDE_CLIENT : TCP4_SIDE_SERVER_WORK, ctxp)); 460} 461 462static int 463tcp4_send(void *ctx, const unsigned char *data, size_t size, int fd) 464{ 465 struct tcp4_ctx *tctx = ctx; 466 467 PJDLOG_ASSERT(tctx != NULL); 468 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 469 PJDLOG_ASSERT(tctx->tc_fd >= 0); 470 PJDLOG_ASSERT(fd == -1); 471 472 return (proto_common_send(tctx->tc_fd, data, size, -1)); 473} 474 475static int 476tcp4_recv(void *ctx, unsigned char *data, size_t size, int *fdp) 477{ 478 struct tcp4_ctx *tctx = ctx; 479 480 PJDLOG_ASSERT(tctx != NULL); 481 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 482 PJDLOG_ASSERT(tctx->tc_fd >= 0); 483 PJDLOG_ASSERT(fdp == NULL); 484 485 return (proto_common_recv(tctx->tc_fd, data, size, NULL)); 486} 487 488static int 489tcp4_descriptor(const void *ctx) 490{ 491 const struct tcp4_ctx *tctx = ctx; 492 493 PJDLOG_ASSERT(tctx != NULL); 494 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 495 496 return (tctx->tc_fd); 497} 498 499static bool 500tcp4_address_match(const void *ctx, const char *addr) 501{ 502 const struct tcp4_ctx *tctx = ctx; 503 struct sockaddr_in sin; 504 socklen_t sinlen; 505 in_addr_t ip1, ip2; 506 507 PJDLOG_ASSERT(tctx != NULL); 508 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 509 510 if (tcp4_addr(addr, HASTD_PORT, &sin) != 0) 511 return (false); 512 ip1 = sin.sin_addr.s_addr; 513 514 sinlen = sizeof(sin); 515 if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) 516 return (false); 517 ip2 = sin.sin_addr.s_addr; 518 519 return (ip1 == ip2); 520} 521 522static void 523tcp4_local_address(const void *ctx, char *addr, size_t size) 524{ 525 const struct tcp4_ctx *tctx = ctx; 526 struct sockaddr_in sin; 527 socklen_t sinlen; 528 529 PJDLOG_ASSERT(tctx != NULL); 530 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 531 532 sinlen = sizeof(sin); 533 if (getsockname(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) { 534 PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size); 535 return; 536 } 537 PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%S", &sin) < (ssize_t)size); 538} 539 540static void 541tcp4_remote_address(const void *ctx, char *addr, size_t size) 542{ 543 const struct tcp4_ctx *tctx = ctx; 544 struct sockaddr_in sin; 545 socklen_t sinlen; 546 547 PJDLOG_ASSERT(tctx != NULL); 548 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 549 550 sinlen = sizeof(sin); 551 if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) { 552 PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size); 553 return; 554 } 555 PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%S", &sin) < (ssize_t)size); 556} 557 558static void 559tcp4_close(void *ctx) 560{ 561 struct tcp4_ctx *tctx = ctx; 562 563 PJDLOG_ASSERT(tctx != NULL); 564 PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 565 566 if (tctx->tc_fd >= 0) 567 close(tctx->tc_fd); 568 tctx->tc_magic = 0; 569 free(tctx); 570} 571 572static struct hast_proto tcp4_proto = { 573 .hp_name = "tcp4", 574 .hp_client = tcp4_client, 575 .hp_connect = tcp4_connect, 576 .hp_connect_wait = tcp4_connect_wait, 577 .hp_server = tcp4_server, 578 .hp_accept = tcp4_accept, 579 .hp_wrap = tcp4_wrap, 580 .hp_send = tcp4_send, 581 .hp_recv = tcp4_recv, 582 .hp_descriptor = tcp4_descriptor, 583 .hp_address_match = tcp4_address_match, 584 .hp_local_address = tcp4_local_address, 585 .hp_remote_address = tcp4_remote_address, 586 .hp_close = tcp4_close 587}; 588 589static __constructor void 590tcp4_ctor(void) 591{ 592 593 proto_register(&tcp4_proto, true); 594} 595