proto_tcp.c revision 210876
1327Sjkh/*- 2327Sjkh * Copyright (c) 2009-2010 The FreeBSD Foundation 3327Sjkh * All rights reserved. 4327Sjkh * 5327Sjkh * This software was developed by Pawel Jakub Dawidek under sponsorship from 6327Sjkh * the FreeBSD Foundation. 7327Sjkh * 8327Sjkh * Redistribution and use in source and binary forms, with or without 9327Sjkh * modification, are permitted provided that the following conditions 10327Sjkh * are met: 11327Sjkh * 1. Redistributions of source code must retain the above copyright 12327Sjkh * notice, this list of conditions and the following disclaimer. 13327Sjkh * 2. Redistributions in binary form must reproduce the above copyright 14327Sjkh * notice, this list of conditions and the following disclaimer in the 15327Sjkh * documentation and/or other materials provided with the distribution. 16327Sjkh * 17327Sjkh * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 1831997Shoek * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19327Sjkh * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20327Sjkh * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 21327Sjkh * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2293520Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2393520Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2493520Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25327Sjkh * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26327Sjkh * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2774699Ssobomax * SUCH DAMAGE. 28327Sjkh */ 29103149Ssobomax 3055567Sphantom#include <sys/cdefs.h> 31327Sjkh__FBSDID("$FreeBSD: head/sbin/hastd/proto_tcp4.c 210876 2010-08-05 18:27:41Z pjd $"); 3272174Ssobomax 33411Sjkh#include <sys/param.h> /* MAXHOSTNAMELEN */ 3484745Ssobomax 3511780Sjkh#include <netinet/in.h> 36392Sjkh#include <netinet/tcp.h> 3796030Ssobomax 3874699Ssobomax#include <assert.h> 39327Sjkh#include <errno.h> 4030221Scharnier#include <fcntl.h> 4130221Scharnier#include <netdb.h> 42327Sjkh#include <stdbool.h> 43327Sjkh#include <stdint.h> 44327Sjkh#include <stdio.h> 45327Sjkh#include <string.h> 46327Sjkh#include <unistd.h> 4756001Sdan 48327Sjkh#include "hast.h" 4974699Ssobomax#include "pjdlog.h" 5074699Ssobomax#include "proto_impl.h" 5174699Ssobomax#include "subr.h" 5274699Ssobomax 5374699Ssobomax#define TCP4_CTX_MAGIC 0x7c441c 54327Sjkhstruct tcp4_ctx { 5546105Sjkh int tc_magic; 5672174Ssobomax struct sockaddr_in tc_sin; 5746105Sjkh int tc_fd; 5846105Sjkh int tc_side; 5946105Sjkh#define TCP4_SIDE_CLIENT 0 60327Sjkh#define TCP4_SIDE_SERVER_LISTEN 1 61327Sjkh#define TCP4_SIDE_SERVER_WORK 2 6272174Ssobomax}; 63327Sjkh 64327Sjkhstatic void tcp4_close(void *ctx); 65327Sjkh 66327Sjkhstatic in_addr_t 67327Sjkhstr2ip(const char *str) 68327Sjkh{ 694996Sjkh struct hostent *hp; 70327Sjkh in_addr_t ip; 71327Sjkh 72327Sjkh ip = inet_addr(str); 73327Sjkh if (ip != INADDR_NONE) { 74327Sjkh /* It is a valid IP address. */ 75327Sjkh return (ip); 76327Sjkh } 77327Sjkh /* Check if it is a valid host name. */ 78327Sjkh hp = gethostbyname(str); 79327Sjkh if (hp == NULL) 80327Sjkh return (INADDR_NONE); 81327Sjkh return (((struct in_addr *)(void *)hp->h_addr)->s_addr); 82327Sjkh} 83327Sjkh 84327Sjkh/* 85327Sjkh * Function converts the given string to unsigned number. 86327Sjkh */ 87327Sjkhstatic int 884996Sjkhnumfromstr(const char *str, intmax_t minnum, intmax_t maxnum, intmax_t *nump) 894996Sjkh{ 904996Sjkh intmax_t digit, num; 914996Sjkh 92327Sjkh if (str[0] == '\0') 93327Sjkh goto invalid; /* Empty string. */ 94327Sjkh num = 0; 95327Sjkh for (; *str != '\0'; str++) { 9671965Sjkh if (*str < '0' || *str > '9') 9771965Sjkh goto invalid; /* Non-digit character. */ 9871965Sjkh digit = *str - '0'; 9971965Sjkh if (num > num * 10 + digit) 10072174Ssobomax goto invalid; /* Overflow. */ 10172174Ssobomax num = num * 10 + digit; 10272174Ssobomax if (num > maxnum) 10372174Ssobomax goto invalid; /* Too big. */ 104327Sjkh } 105327Sjkh if (num < minnum) 106327Sjkh goto invalid; /* Too small. */ 107327Sjkh *nump = num; 108327Sjkh return (0); 109327Sjkhinvalid: 110327Sjkh errno = EINVAL; 111327Sjkh return (-1); 112327Sjkh} 113327Sjkh 114327Sjkhstatic int 115327Sjkhtcp4_addr(const char *addr, struct sockaddr_in *sinp) 1164996Sjkh{ 1174996Sjkh char iporhost[MAXHOSTNAMELEN]; 1184996Sjkh const char *pp; 1194996Sjkh size_t size; 120411Sjkh in_addr_t ip; 121411Sjkh 122411Sjkh if (addr == NULL) 123411Sjkh return (-1); 1244996Sjkh 1254996Sjkh if (strncasecmp(addr, "tcp4://", 7) == 0) 1264996Sjkh addr += 7; 1274996Sjkh else if (strncasecmp(addr, "tcp://", 6) == 0) 12872174Ssobomax addr += 6; 12972174Ssobomax else { 13072174Ssobomax /* 13162775Ssobomax * Because TCP4 is the default assume IP or host is given without 13267454Ssobomax * prefix. 13367454Ssobomax */ 13467454Ssobomax } 13567454Ssobomax 13696030Ssobomax sinp->sin_family = AF_INET; 13796030Ssobomax sinp->sin_len = sizeof(*sinp); 13896030Ssobomax /* Extract optional port. */ 13996030Ssobomax pp = strrchr(addr, ':'); 14096030Ssobomax if (pp == NULL) { 14196030Ssobomax /* Port not given, use the default. */ 14284750Ssobomax sinp->sin_port = htons(HASTD_PORT); 14384750Ssobomax } else { 14484750Ssobomax intmax_t port; 14584750Ssobomax 146379Sjkh if (numfromstr(pp + 1, 1, 65535, &port) < 0) 147379Sjkh return (errno); 148379Sjkh sinp->sin_port = htons(port); 149379Sjkh } 150411Sjkh /* Extract host name or IP address. */ 151411Sjkh if (pp == NULL) { 152411Sjkh size = sizeof(iporhost); 153411Sjkh if (strlcpy(iporhost, addr, size) >= size) 154383Sjkh return (ENAMETOOLONG); 15585019Ssobomax } else { 156383Sjkh size = (size_t)(pp - addr + 1); 157383Sjkh if (size > sizeof(iporhost)) 15872174Ssobomax return (ENAMETOOLONG); 15972174Ssobomax if (strlcpy(iporhost, addr, size) >= size) 16072174Ssobomax return (ENAMETOOLONG); 16172174Ssobomax } 162392Sjkh /* Convert string (IP address or host name) to in_addr_t. */ 163392Sjkh ip = str2ip(iporhost); 164392Sjkh if (ip == INADDR_NONE) 165392Sjkh return (EINVAL); 16674699Ssobomax sinp->sin_addr.s_addr = ip; 16774699Ssobomax 16874699Ssobomax return (0); 16974699Ssobomax} 17074699Ssobomax 17174699Ssobomaxstatic int 17274699Ssobomaxtcp4_common_setup(const char *addr, void **ctxp, int side) 17374699Ssobomax{ 17474699Ssobomax struct tcp4_ctx *tctx; 17574699Ssobomax int ret, val; 17674699Ssobomax 17774699Ssobomax tctx = malloc(sizeof(*tctx)); 17874699Ssobomax if (tctx == NULL) 17974699Ssobomax return (errno); 180103149Ssobomax 181103149Ssobomax /* Parse given address. */ 182103149Ssobomax if ((ret = tcp4_addr(addr, &tctx->tc_sin)) != 0) { 183103149Ssobomax free(tctx); 184327Sjkh return (ret); 185327Sjkh } 186327Sjkh 18730221Scharnier tctx->tc_fd = socket(AF_INET, SOCK_STREAM, 0); 188327Sjkh if (tctx->tc_fd == -1) { 189327Sjkh ret = errno; 19046105Sjkh free(tctx); 191327Sjkh return (ret); 1928857Srgrimes } 193327Sjkh 194327Sjkh /* Socket settings. */ 195103149Ssobomax val = 1; 196103149Ssobomax if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &val, 197103149Ssobomax sizeof(val)) == -1) { 198103149Ssobomax pjdlog_warning("Unable to set TCP_NOELAY on %s", addr); 199103149Ssobomax } 200103149Ssobomax val = 131072; 201103149Ssobomax if (setsockopt(tctx->tc_fd, SOL_SOCKET, SO_SNDBUF, &val, 202327Sjkh sizeof(val)) == -1) { 203327Sjkh pjdlog_warning("Unable to set send buffer size on %s", addr); 2044996Sjkh } 205327Sjkh val = 131072; 206327Sjkh if (setsockopt(tctx->tc_fd, SOL_SOCKET, SO_RCVBUF, &val, 20760563Ssteve sizeof(val)) == -1) { 20895934Ssobomax pjdlog_warning("Unable to set receive buffer size on %s", addr); 20995934Ssobomax } 21095934Ssobomax 21195934Ssobomax tctx->tc_side = side; 21295934Ssobomax tctx->tc_magic = TCP4_CTX_MAGIC; 21372174Ssobomax *ctxp = tctx; 21472174Ssobomax 21572174Ssobomax return (0); 21672174Ssobomax} 21772174Ssobomax 21872174Ssobomaxstatic int 21972174Ssobomaxtcp4_client(const char *addr, void **ctxp) 22072174Ssobomax{ 22172174Ssobomax 22272174Ssobomax return (tcp4_common_setup(addr, ctxp, TCP4_SIDE_CLIENT)); 22372174Ssobomax} 22472174Ssobomax 22572174Ssobomaxstatic int 22660563Sstevetcp4_connect(void *ctx) 22760563Ssteve{ 22856001Sdan struct tcp4_ctx *tctx = ctx; 229327Sjkh struct timeval tv; 230327Sjkh fd_set fdset; 23174699Ssobomax socklen_t esize; 23296030Ssobomax int error, flags, ret; 23330221Scharnier 234327Sjkh assert(tctx != NULL); 235327Sjkh assert(tctx->tc_magic == TCP4_CTX_MAGIC); 236327Sjkh assert(tctx->tc_side == TCP4_SIDE_CLIENT); 237327Sjkh assert(tctx->tc_fd >= 0); 23830221Scharnier 23930221Scharnier flags = fcntl(tctx->tc_fd, F_GETFL); 240327Sjkh if (flags == -1) { 24196067Ssobomax KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno, 24284750Ssobomax "fcntl(F_GETFL) failed")); 24396067Ssobomax return (errno); 24496067Ssobomax } 24596067Ssobomax /* 24630221Scharnier * We make socket non-blocking so we have decided about connection 247327Sjkh * timeout. 248327Sjkh */ 249 flags |= O_NONBLOCK; 250 if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) { 251 KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno, 252 "fcntl(F_SETFL, O_NONBLOCK) failed")); 253 return (errno); 254 } 255 256 if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 257 sizeof(tctx->tc_sin)) == 0) { 258 error = 0; 259 goto done; 260 } 261 if (errno != EINPROGRESS) { 262 error = errno; 263 pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed"); 264 goto done; 265 } 266 /* 267 * Connection can't be established immediately, let's wait 268 * for HAST_TIMEOUT seconds. 269 */ 270 tv.tv_sec = HAST_TIMEOUT; 271 tv.tv_usec = 0; 272again: 273 FD_ZERO(&fdset); 274 FD_SET(tctx->tc_fd, &fdset); 275 ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv); 276 if (ret == 0) { 277 error = ETIMEDOUT; 278 goto done; 279 } else if (ret == -1) { 280 if (errno == EINTR) 281 goto again; 282 error = errno; 283 pjdlog_common(LOG_DEBUG, 1, errno, "select() failed"); 284 goto done; 285 } 286 assert(ret > 0); 287 assert(FD_ISSET(tctx->tc_fd, &fdset)); 288 esize = sizeof(error); 289 if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error, 290 &esize) == -1) { 291 error = errno; 292 pjdlog_common(LOG_DEBUG, 1, errno, 293 "getsockopt(SO_ERROR) failed"); 294 goto done; 295 } 296 if (error != 0) { 297 pjdlog_common(LOG_DEBUG, 1, error, 298 "getsockopt(SO_ERROR) returned error"); 299 goto done; 300 } 301 error = 0; 302done: 303 flags &= ~O_NONBLOCK; 304 if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) { 305 if (error == 0) 306 error = errno; 307 pjdlog_common(LOG_DEBUG, 1, errno, 308 "fcntl(F_SETFL, ~O_NONBLOCK) failed"); 309 } 310 return (error); 311} 312 313static int 314tcp4_server(const char *addr, void **ctxp) 315{ 316 struct tcp4_ctx *tctx; 317 int ret, val; 318 319 ret = tcp4_common_setup(addr, ctxp, TCP4_SIDE_SERVER_LISTEN); 320 if (ret != 0) 321 return (ret); 322 323 tctx = *ctxp; 324 325 val = 1; 326 /* Ignore failure. */ 327 (void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val, 328 sizeof(val)); 329 330 if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 331 sizeof(tctx->tc_sin)) < 0) { 332 ret = errno; 333 tcp4_close(tctx); 334 return (ret); 335 } 336 if (listen(tctx->tc_fd, 8) < 0) { 337 ret = errno; 338 tcp4_close(tctx); 339 return (ret); 340 } 341 342 return (0); 343} 344 345static int 346tcp4_accept(void *ctx, void **newctxp) 347{ 348 struct tcp4_ctx *tctx = ctx; 349 struct tcp4_ctx *newtctx; 350 socklen_t fromlen; 351 int ret; 352 353 assert(tctx != NULL); 354 assert(tctx->tc_magic == TCP4_CTX_MAGIC); 355 assert(tctx->tc_side == TCP4_SIDE_SERVER_LISTEN); 356 assert(tctx->tc_fd >= 0); 357 358 newtctx = malloc(sizeof(*newtctx)); 359 if (newtctx == NULL) 360 return (errno); 361 362 fromlen = sizeof(tctx->tc_sin); 363 newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 364 &fromlen); 365 if (newtctx->tc_fd < 0) { 366 ret = errno; 367 free(newtctx); 368 return (ret); 369 } 370 371 newtctx->tc_side = TCP4_SIDE_SERVER_WORK; 372 newtctx->tc_magic = TCP4_CTX_MAGIC; 373 *newctxp = newtctx; 374 375 return (0); 376} 377 378static int 379tcp4_send(void *ctx, const unsigned char *data, size_t size) 380{ 381 struct tcp4_ctx *tctx = ctx; 382 383 assert(tctx != NULL); 384 assert(tctx->tc_magic == TCP4_CTX_MAGIC); 385 assert(tctx->tc_fd >= 0); 386 387 return (proto_common_send(tctx->tc_fd, data, size)); 388} 389 390static int 391tcp4_recv(void *ctx, unsigned char *data, size_t size) 392{ 393 struct tcp4_ctx *tctx = ctx; 394 395 assert(tctx != NULL); 396 assert(tctx->tc_magic == TCP4_CTX_MAGIC); 397 assert(tctx->tc_fd >= 0); 398 399 return (proto_common_recv(tctx->tc_fd, data, size)); 400} 401 402static int 403tcp4_descriptor(const void *ctx) 404{ 405 const struct tcp4_ctx *tctx = ctx; 406 407 assert(tctx != NULL); 408 assert(tctx->tc_magic == TCP4_CTX_MAGIC); 409 410 return (tctx->tc_fd); 411} 412 413static void 414sin2str(struct sockaddr_in *sinp, char *addr, size_t size) 415{ 416 in_addr_t ip; 417 unsigned int port; 418 419 assert(addr != NULL); 420 assert(sinp->sin_family == AF_INET); 421 422 ip = ntohl(sinp->sin_addr.s_addr); 423 port = ntohs(sinp->sin_port); 424 PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%u.%u.%u.%u:%u", 425 ((ip >> 24) & 0xff), ((ip >> 16) & 0xff), ((ip >> 8) & 0xff), 426 (ip & 0xff), port) < (ssize_t)size); 427} 428 429static bool 430tcp4_address_match(const void *ctx, const char *addr) 431{ 432 const struct tcp4_ctx *tctx = ctx; 433 struct sockaddr_in sin; 434 socklen_t sinlen; 435 in_addr_t ip1, ip2; 436 437 assert(tctx != NULL); 438 assert(tctx->tc_magic == TCP4_CTX_MAGIC); 439 440 if (tcp4_addr(addr, &sin) != 0) 441 return (false); 442 ip1 = sin.sin_addr.s_addr; 443 444 sinlen = sizeof(sin); 445 if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) 446 return (false); 447 ip2 = sin.sin_addr.s_addr; 448 449 return (ip1 == ip2); 450} 451 452static void 453tcp4_local_address(const void *ctx, char *addr, size_t size) 454{ 455 const struct tcp4_ctx *tctx = ctx; 456 struct sockaddr_in sin; 457 socklen_t sinlen; 458 459 assert(tctx != NULL); 460 assert(tctx->tc_magic == TCP4_CTX_MAGIC); 461 462 sinlen = sizeof(sin); 463 if (getsockname(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) { 464 PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size); 465 return; 466 } 467 sin2str(&sin, addr, size); 468} 469 470static void 471tcp4_remote_address(const void *ctx, char *addr, size_t size) 472{ 473 const struct tcp4_ctx *tctx = ctx; 474 struct sockaddr_in sin; 475 socklen_t sinlen; 476 477 assert(tctx != NULL); 478 assert(tctx->tc_magic == TCP4_CTX_MAGIC); 479 480 sinlen = sizeof(sin); 481 if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) { 482 PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size); 483 return; 484 } 485 sin2str(&sin, addr, size); 486} 487 488static void 489tcp4_close(void *ctx) 490{ 491 struct tcp4_ctx *tctx = ctx; 492 493 assert(tctx != NULL); 494 assert(tctx->tc_magic == TCP4_CTX_MAGIC); 495 496 if (tctx->tc_fd >= 0) 497 close(tctx->tc_fd); 498 tctx->tc_magic = 0; 499 free(tctx); 500} 501 502static struct hast_proto tcp4_proto = { 503 .hp_name = "tcp4", 504 .hp_client = tcp4_client, 505 .hp_connect = tcp4_connect, 506 .hp_server = tcp4_server, 507 .hp_accept = tcp4_accept, 508 .hp_send = tcp4_send, 509 .hp_recv = tcp4_recv, 510 .hp_descriptor = tcp4_descriptor, 511 .hp_address_match = tcp4_address_match, 512 .hp_local_address = tcp4_local_address, 513 .hp_remote_address = tcp4_remote_address, 514 .hp_close = tcp4_close 515}; 516 517static __constructor void 518tcp4_ctor(void) 519{ 520 521 proto_register(&tcp4_proto, true); 522} 523