#include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_NETINET_TCP_FSM_H #include #endif #include #include #include #include #include #include #include #include #include #ifndef PIDFILE #define PIDFILE "echo_srv.pid" #endif /* PIDFILE */ #define ECHO_SRV_IPV4 "127.0.0.10" /* socket wrapper IPv6 prefix fd00::5357:5fxx */ #define ECHO_SRV_IPV6 "fd00::5357:5f0a" #define DFL_PORT 7 #define BACKLOG 16 #ifndef BUFSIZE #define BUFSIZE 0x20000 /* 128K */ #endif /* BUFSIZE */ #ifndef discard_const #define discard_const(ptr) ((void *)((uintptr_t)(ptr))) #endif #ifndef discard_const_p #define discard_const_p(type, ptr) ((type *)discard_const(ptr)) #endif #ifndef ZERO_STRUCT #define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) #endif struct torture_address { socklen_t sa_socklen; union { struct sockaddr s; struct sockaddr_in in; #ifdef HAVE_IPV6 struct sockaddr_in6 in6; #endif struct sockaddr_storage ss; } sa; }; struct echo_srv_opts { int port; int socktype; bool daemon; char *bind; const char *pidfile; }; #ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL #if defined(IP_PKTINFO) || defined(IP_RECVDSTADDR) || defined(IPV6_PKTINFO) union pktinfo { #ifdef HAVE_STRUCT_IN6_PKTINFO struct in6_pktinfo pkt6; #endif #ifdef HAVE_STRUCT_IN_PKTINFO struct in_pktinfo pkt4; #elif defined(IP_RECVDSTADDR) struct in_addr pkt4; #endif char c; }; #define HAVE_UNION_PKTINFO 1 #endif /* IP_PKTINFO || IP_RECVDSTADDR || IPV6_PKTINFO */ static const char *echo_server_address(int family) { switch (family) { case AF_INET: { const char *ip4 = getenv("TORTURE_SERVER_ADDRESS_IPV4"); if (ip4 != NULL && ip4[0] != '\0') { return ip4; } return ECHO_SRV_IPV4; } #ifdef HAVE_IPV6 case AF_INET6: { const char *ip6 = getenv("TORTURE_SERVER_ADDRESS_IPV6"); if (ip6 != NULL && ip6[0] != '\0') { return ip6; } return ECHO_SRV_IPV6; } #endif default: return NULL; } return NULL; } #endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ static void _assert_return_code(int rc, int err, const char * const file, const int line) { if (rc < 0) { fprintf(stderr, "Fatal error: %s\n", strerror(err)); fprintf(stderr, "%s:%d", file, line); abort(); } } #define assert_return_code(rc, err) \ _assert_return_code(rc, err, __FILE__, __LINE__) static int pidfile(const char *path) { int err; int fd; char pid_str[32] = { 0 }; ssize_t nwritten; size_t len; fd = open(path, O_RDONLY, 0644); err = errno; if (fd != -1) { close(fd); return EEXIST; } else if (err != ENOENT) { return err; } fd = open(path, O_CREAT | O_WRONLY | O_EXCL, 0644); err = errno; if (fd == -1) { return err; } snprintf(pid_str, sizeof(pid_str) -1, "%u\n", (unsigned int) getpid()); len = strlen(pid_str); nwritten = write(fd, pid_str, len); close(fd); if (nwritten != (ssize_t)len) { return EIO; } return 0; } static int become_daemon(void) { int ret; pid_t child_pid; int fd; int i; if (getppid() == 1) { return 0; } child_pid = fork(); if (child_pid == -1) { ret = errno; perror("fork"); return ret; } else if (child_pid > 0) { exit(0); } /* If a working directory was defined, go there */ #ifdef WORKING_DIR chdir(WORKING_DIR); #endif ret = setsid(); if (ret == -1) { ret = errno; perror("setsid"); return ret; } for (i = 0; i < 3; i++) { close(i); fd = open("/dev/null", O_RDWR, 0); if (fd < 0) { fd = open("/dev/null", O_WRONLY, 0); } if (fd < 0) { ret = errno; perror("Can't open /dev/null"); return ret; } if (fd != i) { perror("Didn't get correct fd"); close(fd); return EINVAL; } } umask(0177); return 0; } static void set_sock_pktinfo(int sock, int family) { int sockopt = 1; int option = 0; int proto = 0; int rc; switch(family) { case AF_INET: proto = IPPROTO_IP; #ifdef IP_PKTINFO option = IP_PKTINFO; #elif IP_RECVDSTADDR option = IP_RECVDSTADDR; #else return; #endif /* IP_PKTINFO */ break; #ifdef HAVE_IPV6 #ifdef IPV6_RECVPKTINFO case AF_INET6: proto = IPPROTO_IPV6; option = IPV6_RECVPKTINFO; break; #endif /* IPV6_RECVPKTINFO */ #endif /* HAVE_IPV6 */ default: return; } rc = setsockopt(sock, proto, option, &sockopt, sizeof(sockopt)); assert_return_code(rc, errno); } /* Returns 0 on success, errno on failure. If successful, * sock is a ready to use socket */ static int setup_srv(struct echo_srv_opts *opts, int *_sock) { struct addrinfo hints; struct addrinfo *res, *ri; char svc[6]; int ret; int sock; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = opts->socktype; hints.ai_flags = AI_PASSIVE; snprintf(svc, sizeof(svc), "%d", opts->port); ret = getaddrinfo(opts->bind, svc, &hints, &res); if (ret != 0) { return errno; } for (ri = res; ri != NULL; ri = ri->ai_next) { sock = socket(ri->ai_family, ri->ai_socktype, ri->ai_protocol); if (sock == -1) { ret = errno; freeaddrinfo(res); perror("socket"); return ret; } if (ri->ai_socktype == SOCK_DGRAM) { set_sock_pktinfo(sock, ri->ai_family); } ret = bind(sock, ri->ai_addr, ri->ai_addrlen); if (ret == 0) { break; } close(sock); } freeaddrinfo(res); if (ri == NULL) { fprintf(stderr, "Could not bind\n"); return EFAULT; } if (opts->socktype == SOCK_STREAM) { ret = listen(sock, BACKLOG); if (ret == -1) { ret = errno; close(sock); perror("listen"); return ret; } #ifdef TCP_INFO { struct tcp_info info; socklen_t optlen = sizeof(info); ZERO_STRUCT(info); ret = getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, &optlen); if (ret == -1) { ret = errno; perror("TCP_INFO failed"); close(sock); return ret; } #ifdef HAVE_NETINET_TCP_FSM_H /* This is FreeBSD */ # define __TCP_LISTEN TCPS_LISTEN #else /* This is Linux */ # define __TCP_LISTEN TCP_LISTEN #endif if (info.tcpi_state != __TCP_LISTEN) { errno = ret = ERANGE; perror("not __TCP_LISTEN => ERANGE..."); close(sock); return ret; } } #endif /* TCP_INFO */ } *_sock = sock; return 0; } static int socket_dup(int s) { struct torture_address cli_addr1 = { .sa_socklen = sizeof(struct sockaddr_storage), }; struct torture_address srv_addr1 = { .sa_socklen = sizeof(struct sockaddr_storage), }; struct torture_address cli_addr2 = { .sa_socklen = sizeof(struct sockaddr_storage), }; struct torture_address srv_addr2 = { .sa_socklen = sizeof(struct sockaddr_storage), }; struct torture_address cli_addr3 = { .sa_socklen = sizeof(struct sockaddr_storage), }; struct torture_address srv_addr3 = { .sa_socklen = sizeof(struct sockaddr_storage), }; int s2; int rc; rc = getsockname(s, &srv_addr1.sa.s, &srv_addr1.sa_socklen); if (rc == -1) { perror("getsockname"); return -1; } rc = getpeername(s, &cli_addr1.sa.s, &cli_addr1.sa_socklen); if (rc == -1) { perror("getpeername"); return -1; } if (cli_addr1.sa.ss.ss_family != srv_addr1.sa.ss.ss_family) { perror("client/server family mismatch"); return -1; } /* Test dup */ s2 = dup(s); if (s2 == -1) { perror("dup"); return -1; } close(s); rc = getsockname(s2, &srv_addr2.sa.s, &srv_addr2.sa_socklen); if (rc == -1) { perror("getsockname"); close(s2); return -1; } rc = getpeername(s2, &cli_addr2.sa.s, &cli_addr2.sa_socklen); if (rc == -1) { perror("getpeername"); close(s2); return -1; } if (cli_addr1.sa_socklen != cli_addr2.sa_socklen || srv_addr1.sa_socklen != srv_addr2.sa_socklen) { perror("length mismatch"); close(s2); return -1; } switch(cli_addr1.sa.ss.ss_family) { case AF_INET: { rc = memcmp(&cli_addr1.sa.in, &cli_addr2.sa.in, sizeof(struct sockaddr_in)); if (rc != 0) { perror("client mismatch"); } rc = memcmp(&srv_addr1.sa.in, &srv_addr2.sa.in, sizeof(struct sockaddr_in)); if (rc != 0) { perror("server mismatch"); } break; } #ifdef HAVE_IPV6 case AF_INET6: { rc = memcmp(&cli_addr1.sa.in6, &cli_addr2.sa.in6, sizeof(struct sockaddr_in6)); if (rc != 0) { perror("client mismatch"); } rc = memcmp(&srv_addr1.sa.in6, &srv_addr2.sa.in6, sizeof(struct sockaddr_in6)); if (rc != 0) { perror("server mismatch"); } break; } #endif default: perror("family mismatch"); close(s2); return -1; } /* Test dup2 */ s = dup2(s2, s); close(s2); if (s == -1) { perror("dup"); return -1; } rc = getsockname(s, &srv_addr3.sa.s, &srv_addr3.sa_socklen); if (rc == -1) { perror("getsockname"); close(s); return -1; } rc = getpeername(s, &cli_addr3.sa.s, &cli_addr3.sa_socklen); if (rc == -1) { perror("getpeername"); close(s); return -1; } if (cli_addr2.sa_socklen != cli_addr3.sa_socklen || srv_addr2.sa_socklen != srv_addr3.sa_socklen) { perror("length mismatch"); close(s); return -1; } switch(cli_addr2.sa.ss.ss_family) { case AF_INET: { rc = memcmp(&cli_addr1.sa.in, &cli_addr2.sa.in, sizeof(struct sockaddr_in)); if (rc != 0) { perror("client mismatch"); } rc = memcmp(&srv_addr1.sa.in, &srv_addr2.sa.in, sizeof(struct sockaddr_in)); if (rc != 0) { perror("server mismatch"); } break; } #ifdef HAVE_IPV6 case AF_INET6: { rc = memcmp(&cli_addr1.sa.in6, &cli_addr2.sa.in6, sizeof(struct sockaddr_in6)); if (rc != 0) { perror("client mismatch"); } rc = memcmp(&srv_addr1.sa.in6, &srv_addr2.sa.in6, sizeof(struct sockaddr_in6)); if (rc != 0) { perror("server mismatch"); } break; } #endif default: perror("family mismatch"); close(s); return -1; } return s; } static void echo_tcp(int sock) { struct torture_address addr = { .sa_socklen = sizeof(struct sockaddr_storage), }; char buf[BUFSIZE]; ssize_t bret; int client_sock = -1; int s; pid_t pid; while (1) { waitpid(-1, NULL, WNOHANG); s = accept(sock, &addr.sa.s, &addr.sa_socklen); if (s == -1 && errno == ECONNABORTED) { continue; } if (s == -1) { perror("accept"); goto done; } pid = fork(); if (pid == -1) { perror("fork"); } else if (pid == 0) { close(sock); client_sock = socket_dup(s); if (client_sock == -1) { perror("socket_dup"); goto done; } while (1) { bret = recv(client_sock, buf, BUFSIZE, 0); if (bret == -1) { perror("recv"); goto done; } else if (bret == 0) { break; } bret = send(client_sock, buf, bret, 0); if (bret == -1) { perror("send"); goto done; } } close(s); exit(0); } close(s); } done: if (client_sock != -1) { close(client_sock); } } static ssize_t echo_udp_recv_from_to(int sock, void *buf, size_t buflen, int flags, struct torture_address *from, struct torture_address *to) { struct msghdr rmsg; struct iovec riov; ssize_t ret; #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && defined(HAVE_UNION_PKTINFO) size_t cmlen = CMSG_LEN(sizeof(union pktinfo)); char cmsg[cmlen]; #endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ riov.iov_base = buf; riov.iov_len = buflen; ZERO_STRUCT(rmsg); rmsg.msg_name = &from->sa.s; rmsg.msg_namelen = from->sa_socklen; rmsg.msg_iov = &riov; rmsg.msg_iovlen = 1; #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && defined(HAVE_UNION_PKTINFO) memset(cmsg, 0, cmlen); rmsg.msg_control = cmsg; rmsg.msg_controllen = cmlen; #endif ret = recvmsg(sock, &rmsg, flags); if (ret < 0) { return ret; } from->sa_socklen = rmsg.msg_namelen; #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && defined(HAVE_UNION_PKTINFO) if (rmsg.msg_controllen > 0) { struct cmsghdr *cmsgptr; cmsgptr = CMSG_FIRSTHDR(&rmsg); while (cmsgptr != NULL) { const char *p; #if defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO) if (cmsgptr->cmsg_level == IPPROTO_IP && cmsgptr->cmsg_type == IP_PKTINFO) { char ip[INET_ADDRSTRLEN] = { 0 }; struct in_pktinfo *pkt; void *cmsg_cast_ptr = CMSG_DATA(cmsgptr); pkt = (struct in_pktinfo *)cmsg_cast_ptr; to->sa.in.sin_family = AF_INET; to->sa.in.sin_addr = pkt->ipi_addr; to->sa_socklen = sizeof(struct sockaddr_in); p = inet_ntop(AF_INET, &to->sa.in.sin_addr, ip, sizeof(ip)); if (p == 0) { fprintf(stderr, "Failed to convert IP address"); abort(); } if (strcmp(ip, echo_server_address(AF_INET)) != 0) { fprintf(stderr, "Wrong IP received"); abort(); } } #endif /* IP_PKTINFO */ #ifdef IP_RECVDSTADDR if (cmsgptr->cmsg_level == IPPROTO_IP && cmsgptr->cmsg_type == IP_RECVDSTADDR) { char ip[INET_ADDRSTRLEN] = { 0 }; struct in_addr *addr; void *cmsg_cast_ptr = CMSG_DATA(cmsgptr); addr = (struct in_addr *)cmsg_cast_ptr; to->sa.in.sin_family = AF_INET; to->sa.in.sin_addr = *addr; to->sa_socklen = sizeof(struct sockaddr_in); p = inet_ntop(AF_INET, &to->sa.in.sin_addr, ip, sizeof(ip)); if (p == 0) { fprintf(stderr, "Failed to convert IP address"); abort(); } if (strcmp(ip, echo_server_address(AF_INET)) != 0) { fprintf(stderr, "Wrong IP received"); abort(); } } #endif /* IP_RECVDSTADDR */ #if defined(IPV6_PKTINFO) && defined(HAVE_STRUCT_IN6_PKTINFO) if (cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_PKTINFO) { char ip[INET6_ADDRSTRLEN] = { 0 }; struct in6_pktinfo *pkt6; void *cmsg_cast_ptr = CMSG_DATA(cmsgptr); pkt6 = (struct in6_pktinfo *)cmsg_cast_ptr; to->sa.in6.sin6_family = AF_INET6; to->sa.in6.sin6_addr = pkt6->ipi6_addr; p = inet_ntop(AF_INET6, &to->sa.in6.sin6_addr, ip, sizeof(ip)); if (p == 0) { fprintf(stderr, "Failed to convert IP address"); abort(); } if (strcmp(ip, echo_server_address(AF_INET6)) != 0) { fprintf(stderr, "Wrong IP received"); abort(); } } #endif /* IPV6_PKTINFO */ cmsgptr = CMSG_NXTHDR(&rmsg, cmsgptr); } } else { fprintf(stderr, "Failed to receive pktinfo"); abort(); } #endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && HAVE_UNION_PKTINFO */ return ret; } static ssize_t echo_udp_send_to_from(int sock, void *buf, size_t buflen, int flags, struct sockaddr *to, socklen_t tolen, struct sockaddr *from, socklen_t fromlen) { struct msghdr msg; struct iovec iov; ssize_t ret; #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && defined(HAVE_UNION_PKTINFO) size_t clen = CMSG_SPACE(sizeof(union pktinfo)); char cbuf[clen]; struct cmsghdr *cmsgptr; #else (void)from; /* unused */ (void)fromlen; /* unused */ #endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && HAVE_UNION_PKTINFO */ iov.iov_base = buf; iov.iov_len = buflen; ZERO_STRUCT(msg); msg.msg_name = to; msg.msg_namelen = tolen; msg.msg_iov = &iov; msg.msg_iovlen = 1; #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && defined(HAVE_UNION_PKTINFO) memset(cbuf, 0, clen); msg.msg_control = cbuf; msg.msg_controllen = clen; cmsgptr = CMSG_FIRSTHDR(&msg); msg.msg_controllen = 0; switch (from->sa_family) { #if defined(IP_PKTINFO) || defined(IP_SENDSRCADDR) case AF_INET: { void *cmsg_cast_ptr = CMSG_DATA(cmsgptr); #ifdef IP_PKTINFO struct in_pktinfo *p = (struct in_pktinfo *)cmsg_cast_ptr; #elif defined(IP_SENDSRCADDR) struct in_addr *p = (struct in_addr *)cmsg_cast_ptr; #endif const struct sockaddr_in *from4 = (const struct sockaddr_in *)(const void *)from; if (fromlen != sizeof(struct sockaddr_in)) { break; } cmsgptr->cmsg_level = IPPROTO_IP; #ifdef IP_PKTINFO cmsgptr->cmsg_type = IP_PKTINFO; p->ipi_spec_dst = from4->sin_addr; #elif defined(IP_SENDSRCADDR) cmsgptr->cmsg_type = IP_SENDSRCADDR; *p = from4->sin_addr; #endif cmsgptr->cmsg_len = CMSG_LEN(sizeof(*p)); msg.msg_controllen = CMSG_SPACE(sizeof(*p)); break; } #endif /* IP_PKTINFO || IP_SENDSRCADDR */ #ifdef IPV6_PKTINFO case AF_INET6: { void *cast_ptr = CMSG_DATA(cmsgptr); struct in6_pktinfo *p = (struct in6_pktinfo *)cast_ptr; const struct sockaddr_in6 *from6 = (const struct sockaddr_in6 *)(const void *)from; if (fromlen != sizeof(struct sockaddr_in6)) { break; } cmsgptr->cmsg_level = IPPROTO_IPV6; cmsgptr->cmsg_type = IPV6_PKTINFO; cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); p->ipi6_addr = from6->sin6_addr; msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); break; } #endif /* IPV6_PKTINFO */ default: break; } #endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && HAVE_UNION_PKTINFO */ ret = sendmsg(sock, &msg, flags); return ret; } static void echo_udp(int sock) { struct torture_address saddr = { .sa_socklen = sizeof(struct sockaddr_storage), }; struct torture_address daddr = { .sa_socklen = sizeof(struct sockaddr_storage), }; ssize_t bret; char buf[BUFSIZE]; while (1) { bret = echo_udp_recv_from_to(sock, buf, BUFSIZE, 0, &saddr, &daddr); if (bret == -1) { perror("recvfrom"); continue; } bret = echo_udp_send_to_from(sock, buf, bret, 0, &saddr.sa.s, saddr.sa_socklen, &daddr.sa.s, daddr.sa_socklen); if (bret == -1) { perror("sendto"); continue; } } } static void echo(int sock, struct echo_srv_opts *opts) { switch (opts->socktype) { case SOCK_STREAM: echo_tcp(sock); return; case SOCK_DGRAM: echo_udp(sock); return; default: fprintf(stderr, "Unsupported protocol\n"); return; } } int main(int argc, char **argv) { int ret; int sock = -1; struct echo_srv_opts opts; int opt; int optindex; static struct option long_options[] = { { discard_const_p(char, "tcp"), no_argument, 0, 't' }, { discard_const_p(char, "udp"), no_argument, 0, 'u' }, { discard_const_p(char, "bind-addr"), required_argument, 0, 'b' }, { discard_const_p(char, "port"), required_argument, 0, 'p' }, { discard_const_p(char, "daemon"), no_argument, 0, 'D' }, { discard_const_p(char, "pid"), required_argument, 0, 0 }, {0, 0, 0, 0 } }; opts.port = DFL_PORT; opts.socktype = SOCK_STREAM; opts.bind = NULL; opts.pidfile = PIDFILE; opts.daemon = false; while ((opt = getopt_long(argc, argv, "Dutp:b:", long_options, &optindex)) != -1) { switch (opt) { case 0: if (optindex == 5) { opts.pidfile = optarg; } break; case 'p': opts.port = atoi(optarg); break; case 'b': opts.bind = optarg; break; case 'u': opts.socktype = SOCK_DGRAM; break; case 't': opts.socktype = SOCK_STREAM; break; case 'D': opts.daemon = true; break; default: /* '?' */ fprintf(stderr, "Usage: %s [-p port] [-u] [-t] [-b bind_addr] " \ "[-D] [--pid pidfile]\n" "-t makes the server listen on TCP\n" "-u makes the server listen on UDP\n" "-D tells the server to become a daemon and " \ "write a PIDfile\n" "The default port is 7, the default PIDfile is " \ "echo_srv.pid in the current directory\n", argv[0]); ret = 1; goto done; } } if (opts.daemon) { ret = become_daemon(); if (ret != 0) { fprintf(stderr, "Cannot become daemon: %s\n", strerror(ret)); goto done; } } ret = setup_srv(&opts, &sock); if (ret != 0) { fprintf(stderr, "Cannot setup server: %s\n", strerror(ret)); goto done; } if (opts.daemon && opts.pidfile != NULL) { ret = pidfile(opts.pidfile); if (ret != 0) { fprintf(stderr, "Cannot create pidfile %s: %s\n", opts.pidfile, strerror(ret)); goto done; } } echo(sock, &opts); if (sock >= 0) { close(sock); } if (opts.daemon && opts.pidfile != NULL) { unlink(opts.pidfile); } ret = 0; done: return ret; }