proto_tcp.c revision 218192
1185089Sraj/*- 2209131Sraj * Copyright (c) 2009-2010 The FreeBSD Foundation 3209131Sraj * All rights reserved. 4240489Sgber * 5185089Sraj * This software was developed by Pawel Jakub Dawidek under sponsorship from 6185089Sraj * the FreeBSD Foundation. 7185089Sraj * 8185089Sraj * Redistribution and use in source and binary forms, with or without 9209131Sraj * modification, are permitted provided that the following conditions 10209131Sraj * are met: 11209131Sraj * 1. Redistributions of source code must retain the above copyright 12185089Sraj * notice, this list of conditions and the following disclaimer. 13185089Sraj * 2. Redistributions in binary form must reproduce the above copyright 14185089Sraj * notice, this list of conditions and the following disclaimer in the 15185089Sraj * documentation and/or other materials provided with the distribution. 16185089Sraj * 17185089Sraj * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 18185089Sraj * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19185089Sraj * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20185089Sraj * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 21185089Sraj * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22185089Sraj * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23185089Sraj * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24185089Sraj * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25185089Sraj * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26185089Sraj * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27185089Sraj * SUCH DAMAGE. 28185089Sraj */ 29185089Sraj 30185089Sraj#include <sys/cdefs.h> 31185089Sraj__FBSDID("$FreeBSD: head/sbin/hastd/proto_tcp4.c 218192 2011-02-02 15:42:00Z pjd $"); 32185089Sraj 33185089Sraj#include <sys/param.h> /* MAXHOSTNAMELEN */ 34185089Sraj 35185089Sraj#include <netinet/in.h> 36185089Sraj#include <netinet/tcp.h> 37185089Sraj 38185089Sraj#include <errno.h> 39185089Sraj#include <fcntl.h> 40185089Sraj#include <netdb.h> 41185089Sraj#include <stdbool.h> 42185089Sraj#include <stdint.h> 43185089Sraj#include <stdio.h> 44185089Sraj#include <string.h> 45185089Sraj#include <unistd.h> 46185089Sraj 47185089Sraj#include "hast.h" 48185089Sraj#include "pjdlog.h" 49185089Sraj#include "proto_impl.h" 50185089Sraj#include "subr.h" 51185089Sraj 52185089Sraj#define TCP4_CTX_MAGIC 0x7c441c 53185089Srajstruct tcp4_ctx { 54185089Sraj int tc_magic; 55185089Sraj struct sockaddr_in tc_sin; 56260327Snwhitehorn int tc_fd; 57240493Sgber int tc_side; 58240493Sgber#define TCP4_SIDE_CLIENT 0 59185089Sraj#define TCP4_SIDE_SERVER_LISTEN 1 60185089Sraj#define TCP4_SIDE_SERVER_WORK 2 61185089Sraj}; 62209131Sraj 63209131Srajstatic void tcp4_close(void *ctx); 64259484Snwhitehorn 65209131Srajstatic in_addr_t 66185089Srajstr2ip(const char *str) 67185089Sraj{ 68185089Sraj struct hostent *hp; 69185089Sraj in_addr_t ip; 70209131Sraj 71185089Sraj ip = inet_addr(str); 72185089Sraj if (ip != INADDR_NONE) { 73260340Sian /* It is a valid IP address. */ 74185089Sraj return (ip); 75185089Sraj } 76185089Sraj /* Check if it is a valid host name. */ 77185089Sraj hp = gethostbyname(str); 78185089Sraj if (hp == NULL) 79209131Sraj return (INADDR_NONE); 80185089Sraj return (((struct in_addr *)(void *)hp->h_addr)->s_addr); 81240493Sgber} 82240493Sgber 83240493Sgber/* 84240493Sgber * Function converts the given string to unsigned number. 85240493Sgber */ 86240493Sgberstatic int 87260340Siannumfromstr(const char *str, intmax_t minnum, intmax_t maxnum, intmax_t *nump) 88260340Sian{ 89260340Sian intmax_t digit, num; 90260340Sian 91260340Sian if (str[0] == '\0') 92260340Sian goto invalid; /* Empty string. */ 93260340Sian num = 0; 94260340Sian for (; *str != '\0'; str++) { 95260340Sian if (*str < '0' || *str > '9') 96260340Sian goto invalid; /* Non-digit character. */ 97260340Sian digit = *str - '0'; 98260340Sian if (num > num * 10 + digit) 99260340Sian goto invalid; /* Overflow. */ 100260340Sian num = num * 10 + digit; 101260340Sian if (num > maxnum) 102260340Sian goto invalid; /* Too big. */ 103260340Sian } 104260340Sian if (num < minnum) 105260340Sian goto invalid; /* Too small. */ 106260340Sian *nump = num; 107260340Sian return (0); 108260340Sianinvalid: 109260340Sian errno = EINVAL; 110260340Sian return (-1); 111260340Sian} 112260340Sian 113260340Sianstatic int 114260340Siantcp4_addr(const char *addr, struct sockaddr_in *sinp) 115260340Sian{ 116260340Sian char iporhost[MAXHOSTNAMELEN]; 117260340Sian const char *pp; 118260340Sian size_t size; 119260340Sian in_addr_t ip; 120260340Sian 121260340Sian if (addr == NULL) 122260340Sian return (-1); 123260340Sian 124260340Sian if (strncasecmp(addr, "tcp4://", 7) == 0) 125260340Sian addr += 7; 126260340Sian else if (strncasecmp(addr, "tcp://", 6) == 0) 127260340Sian addr += 6; 128260340Sian else { 129260340Sian /* 130260340Sian * Because TCP4 is the default assume IP or host is given without 131260340Sian * prefix. 132260340Sian */ 133260340Sian } 134260340Sian 135260340Sian sinp->sin_family = AF_INET; 136260340Sian sinp->sin_len = sizeof(*sinp); 137260340Sian /* Extract optional port. */ 138260340Sian pp = strrchr(addr, ':'); 139260340Sian if (pp == NULL) { 140260340Sian /* Port not given, use the default. */ 141260340Sian sinp->sin_port = htons(HASTD_PORT); 142260340Sian } else { 143260340Sian intmax_t port; 144260340Sian 145260340Sian if (numfromstr(pp + 1, 1, 65535, &port) < 0) 146260340Sian return (errno); 147260340Sian sinp->sin_port = htons(port); 148260340Sian } 149260340Sian /* Extract host name or IP address. */ 150260340Sian if (pp == NULL) { 151260340Sian size = sizeof(iporhost); 152260340Sian if (strlcpy(iporhost, addr, size) >= size) 153260340Sian return (ENAMETOOLONG); 154260340Sian } else { 155260340Sian size = (size_t)(pp - addr + 1); 156260340Sian if (size > sizeof(iporhost)) 157260340Sian return (ENAMETOOLONG); 158260340Sian (void)strlcpy(iporhost, addr, size); 159260340Sian } 160260340Sian /* Convert string (IP address or host name) to in_addr_t. */ 161260340Sian ip = str2ip(iporhost); 162260340Sian if (ip == INADDR_NONE) 163260340Sian return (EINVAL); 164260340Sian sinp->sin_addr.s_addr = ip; 165260340Sian 166260340Sian return (0); 167260340Sian} 168260340Sian 169260340Sianstatic int 170260340Siantcp4_common_setup(const char *addr, void **ctxp, int side) 171260340Sian{ 172260340Sian struct tcp4_ctx *tctx; 173260340Sian int ret, nodelay; 174260340Sian 175260340Sian tctx = malloc(sizeof(*tctx)); 176260340Sian if (tctx == NULL) 177260340Sian return (errno); 178260340Sian 179260340Sian /* Parse given address. */ 180260340Sian if ((ret = tcp4_addr(addr, &tctx->tc_sin)) != 0) { 181260340Sian free(tctx); 182260340Sian return (ret); 183260340Sian } 184260340Sian 185260340Sian tctx->tc_fd = socket(AF_INET, SOCK_STREAM, 0); 186260340Sian if (tctx->tc_fd == -1) { 187260340Sian ret = errno; 188260340Sian free(tctx); 189260340Sian return (ret); 190260340Sian } 191260340Sian 192260340Sian /* Socket settings. */ 193260340Sian nodelay = 1; 194260340Sian if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, 195260340Sian sizeof(nodelay)) == -1) { 196260340Sian pjdlog_warning("Unable to set TCP_NOELAY on %s", addr); 197260340Sian } 198260340Sian 199260340Sian tctx->tc_side = side; 200260340Sian tctx->tc_magic = TCP4_CTX_MAGIC; 201260340Sian *ctxp = tctx; 202260340Sian 203260340Sian return (0); 204260340Sian} 205260340Sian 206260340Sianstatic int 207260340Siantcp4_client(const char *addr, void **ctxp) 208260340Sian{ 209260340Sian 210260340Sian return (tcp4_common_setup(addr, ctxp, TCP4_SIDE_CLIENT)); 211260340Sian} 212260340Sian 213260340Sianstatic int 214260340Siantcp4_connect(void *ctx, int timeout) 215260340Sian{ 216260340Sian struct tcp4_ctx *tctx = ctx; 217260340Sian struct timeval tv; 218260340Sian fd_set fdset; 219260340Sian socklen_t esize; 220260340Sian int error, flags, ret; 221260340Sian 222260340Sian PJDLOG_ASSERT(tctx != NULL); 223260340Sian PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 224260340Sian PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_CLIENT); 225260340Sian PJDLOG_ASSERT(tctx->tc_fd >= 0); 226260340Sian PJDLOG_ASSERT(timeout >= 0); 227260340Sian 228260340Sian flags = fcntl(tctx->tc_fd, F_GETFL); 229260340Sian if (flags == -1) { 230260340Sian KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno, 231260340Sian "fcntl(F_GETFL) failed")); 232260340Sian return (errno); 233260340Sian } 234260340Sian /* 235260340Sian * We make socket non-blocking so we can handle connection timeout 236260340Sian * manually. 237260340Sian */ 238265852Sian flags |= O_NONBLOCK; 239260340Sian if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) { 240260340Sian KEEP_ERRNO(pjdlog_common(LOG_DEBUG, 1, errno, 241260340Sian "fcntl(F_SETFL, O_NONBLOCK) failed")); 242260340Sian return (errno); 243260340Sian } 244260340Sian 245265852Sian if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 246260340Sian sizeof(tctx->tc_sin)) == 0) { 247260340Sian error = 0; 248260340Sian goto done; 249260340Sian } 250260340Sian if (errno != EINPROGRESS) { 251260340Sian error = errno; 252260340Sian pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed"); 253258780Seadler goto done; 254185089Sraj } 255185089Sraj /* 256185089Sraj * Connection can't be established immediately, let's wait 257185089Sraj * for HAST_TIMEOUT seconds. 258185089Sraj */ 259185089Sraj tv.tv_sec = timeout; 260185089Sraj tv.tv_usec = 0; 261185089Srajagain: 262185089Sraj FD_ZERO(&fdset); 263185089Sraj FD_SET(tctx->tc_fd, &fdset); 264185089Sraj ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv); 265185089Sraj if (ret == 0) { 266185089Sraj error = ETIMEDOUT; 267185089Sraj goto done; 268185089Sraj } else if (ret == -1) { 269240489Sgber if (errno == EINTR) 270240489Sgber goto again; 271185089Sraj error = errno; 272240489Sgber pjdlog_common(LOG_DEBUG, 1, errno, "select() failed"); 273185089Sraj goto done; 274240489Sgber } 275240489Sgber PJDLOG_ASSERT(ret > 0); 276185089Sraj PJDLOG_ASSERT(FD_ISSET(tctx->tc_fd, &fdset)); 277240489Sgber esize = sizeof(error); 278240489Sgber if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error, 279240489Sgber &esize) == -1) { 280240489Sgber error = errno; 281240489Sgber pjdlog_common(LOG_DEBUG, 1, errno, 282240489Sgber "getsockopt(SO_ERROR) failed"); 283209131Sraj goto done; 284185089Sraj } 285185089Sraj if (error != 0) { 286209131Sraj pjdlog_common(LOG_DEBUG, 1, error, 287209131Sraj "getsockopt(SO_ERROR) returned error"); 288209131Sraj goto done; 289240489Sgber } 290240489Sgber error = 0; 291240489Sgberdone: 292209131Sraj flags &= ~O_NONBLOCK; 293185089Sraj if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) { 294209131Sraj if (error == 0) 295209131Sraj error = errno; 296209131Sraj pjdlog_common(LOG_DEBUG, 1, errno, 297240489Sgber "fcntl(F_SETFL, ~O_NONBLOCK) failed"); 298240489Sgber } 299209131Sraj return (error); 300185089Sraj} 301185089Sraj 302185089Srajstatic int 303185089Srajtcp4_server(const char *addr, void **ctxp) 304185089Sraj{ 305185089Sraj struct tcp4_ctx *tctx; 306240493Sgber int ret, val; 307240493Sgber 308240493Sgber ret = tcp4_common_setup(addr, ctxp, TCP4_SIDE_SERVER_LISTEN); 309185089Sraj if (ret != 0) 310185089Sraj return (ret); 311209131Sraj 312240489Sgber tctx = *ctxp; 313185089Sraj 314259484Snwhitehorn val = 1; 315185089Sraj /* Ignore failure. */ 316185089Sraj (void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val, 317209131Sraj sizeof(val)); 318209131Sraj 319209131Sraj if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 320209131Sraj sizeof(tctx->tc_sin)) < 0) { 321209131Sraj ret = errno; 322209131Sraj tcp4_close(tctx); 323209131Sraj return (ret); 324209131Sraj } 325209131Sraj if (listen(tctx->tc_fd, 8) < 0) { 326209131Sraj ret = errno; 327209131Sraj tcp4_close(tctx); 328240489Sgber return (ret); 329240489Sgber } 330185089Sraj 331209131Sraj return (0); 332209131Sraj} 333209131Sraj 334209131Srajstatic int 335209131Srajtcp4_accept(void *ctx, void **newctxp) 336185089Sraj{ 337209131Sraj struct tcp4_ctx *tctx = ctx; 338185089Sraj struct tcp4_ctx *newtctx; 339209131Sraj socklen_t fromlen; 340209131Sraj int ret; 341185089Sraj 342209131Sraj PJDLOG_ASSERT(tctx != NULL); 343209131Sraj PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 344209131Sraj PJDLOG_ASSERT(tctx->tc_side == TCP4_SIDE_SERVER_LISTEN); 345185089Sraj PJDLOG_ASSERT(tctx->tc_fd >= 0); 346209131Sraj 347240493Sgber newtctx = malloc(sizeof(*newtctx)); 348240493Sgber if (newtctx == NULL) 349240493Sgber return (errno); 350240493Sgber 351240493Sgber fromlen = sizeof(tctx->tc_sin); 352185089Sraj newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sin, 353185089Sraj &fromlen); 354185089Sraj if (newtctx->tc_fd < 0) { 355185089Sraj ret = errno; 356209131Sraj free(newtctx); 357185089Sraj return (ret); 358209131Sraj } 359209131Sraj 360185089Sraj newtctx->tc_side = TCP4_SIDE_SERVER_WORK; 361185089Sraj newtctx->tc_magic = TCP4_CTX_MAGIC; 362209131Sraj *newctxp = newtctx; 363209131Sraj 364209131Sraj return (0); 365209131Sraj} 366185089Sraj 367185089Srajstatic int 368185089Srajtcp4_send(void *ctx, const unsigned char *data, size_t size) 369185089Sraj{ 370185089Sraj struct tcp4_ctx *tctx = ctx; 371185089Sraj 372209131Sraj PJDLOG_ASSERT(tctx != NULL); 373209131Sraj PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 374209131Sraj PJDLOG_ASSERT(tctx->tc_fd >= 0); 375209131Sraj 376240493Sgber return (proto_common_send(tctx->tc_fd, data, size)); 377240493Sgber} 378240493Sgber 379240493Sgberstatic int 380240493Sgbertcp4_recv(void *ctx, unsigned char *data, size_t size) 381240493Sgber{ 382240493Sgber struct tcp4_ctx *tctx = ctx; 383209131Sraj 384209131Sraj PJDLOG_ASSERT(tctx != NULL); 385209131Sraj PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 386209131Sraj PJDLOG_ASSERT(tctx->tc_fd >= 0); 387209131Sraj 388209131Sraj return (proto_common_recv(tctx->tc_fd, data, size)); 389209131Sraj} 390227843Smarius 391185089Srajstatic int 392185089Srajtcp4_descriptor(const void *ctx) 393209131Sraj{ 394185089Sraj const struct tcp4_ctx *tctx = ctx; 395209131Sraj 396209131Sraj PJDLOG_ASSERT(tctx != NULL); 397185089Sraj PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 398185089Sraj 399185089Sraj return (tctx->tc_fd); 400185089Sraj} 401261513Snwhitehorn 402185089Srajstatic void 403185089Srajsin2str(struct sockaddr_in *sinp, char *addr, size_t size) 404185089Sraj{ 405185089Sraj in_addr_t ip; 406209131Sraj unsigned int port; 407185089Sraj 408218228Smarcel PJDLOG_ASSERT(addr != NULL); 409185089Sraj PJDLOG_ASSERT(sinp->sin_family == AF_INET); 410218228Smarcel 411218228Smarcel ip = ntohl(sinp->sin_addr.s_addr); 412209131Sraj port = ntohs(sinp->sin_port); 413218228Smarcel PJDLOG_VERIFY(snprintf(addr, size, "tcp4://%u.%u.%u.%u:%u", 414259484Snwhitehorn ((ip >> 24) & 0xff), ((ip >> 16) & 0xff), ((ip >> 8) & 0xff), 415259484Snwhitehorn (ip & 0xff), port) < (ssize_t)size); 416209131Sraj} 417185089Sraj 418209131Srajstatic bool 419209131Srajtcp4_address_match(const void *ctx, const char *addr) 420185089Sraj{ 421185089Sraj const struct tcp4_ctx *tctx = ctx; 422185089Sraj struct sockaddr_in sin; 423209131Sraj socklen_t sinlen; 424185089Sraj in_addr_t ip1, ip2; 425209131Sraj 426209131Sraj PJDLOG_ASSERT(tctx != NULL); 427240489Sgber PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 428209131Sraj 429185089Sraj if (tcp4_addr(addr, &sin) != 0) 430185089Sraj return (false); 431209131Sraj ip1 = sin.sin_addr.s_addr; 432240489Sgber 433185089Sraj sinlen = sizeof(sin); 434240489Sgber if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) 435218228Smarcel return (false); 436218228Smarcel ip2 = sin.sin_addr.s_addr; 437218228Smarcel 438209131Sraj return (ip1 == ip2); 439240489Sgber} 440240489Sgber 441240489Sgberstatic void 442218228Smarceltcp4_local_address(const void *ctx, char *addr, size_t size) 443209131Sraj{ 444240489Sgber const struct tcp4_ctx *tctx = ctx; 445209131Sraj struct sockaddr_in sin; 446209131Sraj socklen_t sinlen; 447209131Sraj 448185089Sraj PJDLOG_ASSERT(tctx != NULL); 449185089Sraj PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 450209131Sraj 451209131Sraj sinlen = sizeof(sin); 452209131Sraj if (getsockname(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) { 453185089Sraj PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size); 454185089Sraj return; 455185089Sraj } 456185089Sraj sin2str(&sin, addr, size); 457209131Sraj} 458185089Sraj 459185089Srajstatic void 460185089Srajtcp4_remote_address(const void *ctx, char *addr, size_t size) 461185089Sraj{ 462185089Sraj const struct tcp4_ctx *tctx = ctx; 463240489Sgber struct sockaddr_in sin; 464240489Sgber socklen_t sinlen; 465240489Sgber 466240489Sgber PJDLOG_ASSERT(tctx != NULL); 467209131Sraj PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 468240489Sgber 469240489Sgber sinlen = sizeof(sin); 470259484Snwhitehorn if (getpeername(tctx->tc_fd, (struct sockaddr *)&sin, &sinlen) < 0) { 471259484Snwhitehorn PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size); 472240489Sgber return; 473240489Sgber } 474209131Sraj sin2str(&sin, addr, size); 475209131Sraj} 476209131Sraj 477209131Srajstatic void 478209131Srajtcp4_close(void *ctx) 479209131Sraj{ 480209131Sraj struct tcp4_ctx *tctx = ctx; 481209131Sraj 482240489Sgber PJDLOG_ASSERT(tctx != NULL); 483209131Sraj PJDLOG_ASSERT(tctx->tc_magic == TCP4_CTX_MAGIC); 484240489Sgber 485185089Sraj if (tctx->tc_fd >= 0) 486240489Sgber close(tctx->tc_fd); 487240489Sgber tctx->tc_magic = 0; 488240489Sgber free(tctx); 489240489Sgber} 490240489Sgber 491240489Sgberstatic struct hast_proto tcp4_proto = { 492185089Sraj .hp_name = "tcp4", 493240489Sgber .hp_client = tcp4_client, 494240489Sgber .hp_connect = tcp4_connect, 495240489Sgber .hp_server = tcp4_server, 496240489Sgber .hp_accept = tcp4_accept, 497240489Sgber .hp_send = tcp4_send, 498240489Sgber .hp_recv = tcp4_recv, 499240489Sgber .hp_descriptor = tcp4_descriptor, 500240489Sgber .hp_address_match = tcp4_address_match, 501240489Sgber .hp_local_address = tcp4_local_address, 502240489Sgber .hp_remote_address = tcp4_remote_address, 503240489Sgber .hp_close = tcp4_close 504240489Sgber}; 505240489Sgber 506240489Sgberstatic __constructor void 507240493Sgbertcp4_ctor(void) 508240489Sgber{ 509240489Sgber 510240489Sgber proto_register(&tcp4_proto, true); 511240489Sgber} 512240489Sgber