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