diff options
Diffstat (limited to 'toys/pending/tcpsvd.c')
-rw-r--r-- | toys/pending/tcpsvd.c | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/toys/pending/tcpsvd.c b/toys/pending/tcpsvd.c new file mode 100644 index 00000000..ec664910 --- /dev/null +++ b/toys/pending/tcpsvd.c @@ -0,0 +1,403 @@ +/* tcpsvd.c - TCP(UDP)/IP service daemon + * + * Copyright 2013 Ashwini Kumar <ak.ashwini@gmail.com> + * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com> + * Copyright 2013 Kyungwan Han <asura321@gmail.com> + * + * No Standard. + +USE_TCPSVD(NEWTOY(tcpsvd, "^<3c#=30<1C:b#=20<0u:l:hEv", TOYFLAG_USR|TOYFLAG_BIN)) +USE_TCPSVD(OLDTOY(udpsvd, tcpsvd, OPTSTR_tcpsvd, TOYFLAG_USR|TOYFLAG_BIN)) + +config TCPSVD + bool "tcpsvd" + default y + help + usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog + udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog + + Create TCP/UDP socket, bind to IP:PORT and listen for incoming connection. + Run PROG for each connection. + + IP IP to listen on, 0 = all + PORT Port to listen on + PROG ARGS Program to run + -l NAME Local hostname (else looks up local hostname in DNS) + -u USER[:GRP] Change to user/group after bind + -c N Handle up to N (> 0) connections simultaneously + -b N (TCP Only) Allow a backlog of approximately N TCP SYNs + -C N[:MSG] (TCP Only) Allow only up to N (> 0) connections from the same IP + New connections from this IP address are closed + immediately. MSG is written to the peer before close + -h Look up peer's hostname + -E Don't set up environment variables + -v Verbose +*/ + +#define FOR_tcpsvd +#include "toys.h" + +GLOBALS( + char *name; + char *user; + long bn; + char *nmsg; + long cn; + + int maxc; + int count_all; + int udp; +) + +struct list_pid { + struct list_pid *next; + char *ip; + int pid; +}; + +struct list { + struct list* next; + char *d; + int count; +}; + +struct hashed { + struct list *head; +}; + +#define HASH_NR 256 +struct hashed h[HASH_NR]; +struct list_pid *pids = NULL; + +// convert IP address to string. +static char *sock_to_address(struct sockaddr *sock, int flags) +{ + char hbuf[NI_MAXHOST] = {0,}; + char sbuf[NI_MAXSERV] = {0,}; + int status = 0; + socklen_t len = sizeof(struct sockaddr_in6); + + if (!(status = getnameinfo(sock, len, hbuf, sizeof(hbuf), sbuf, + sizeof(sbuf), flags))) { + if (flags & NI_NUMERICSERV) return xmsprintf("%s:%s",hbuf, sbuf); + return xmsprintf("%s",hbuf); + } + error_exit("getnameinfo: %s", gai_strerror(status)); +} + +// Insert pid, ip and fd in the list. +static void insert(struct list_pid **l, int pid, char *addr) +{ + struct list_pid *newnode = xmalloc(sizeof(struct list_pid)); + newnode->pid = pid; + newnode->ip = addr; + newnode->next = NULL; + if (!*l) *l = newnode; + else { + newnode->next = (*l); + *l = newnode; + } +} + +// Hashing of IP address. +static int haship( char *addr) +{ + uint32_t ip[8] = {0,}; + int count = 0, i = 0; + + if (!addr) error_exit("NULL ip"); + while (i < strlen(addr)) { + while (addr[i] && (addr[i] != ':') && (addr[i] != '.')) { + ip[count] = ip[count]*10 + (addr[i]-'0'); + i++; + } + if (i >= strlen(addr)) break; + count++; + i++; + } + return (ip[0]^ip[1]^ip[2]^ip[3]^ip[4]^ip[5]^ip[6]^ip[7])%HASH_NR; +} + +// Remove a node from the list. +static char *delete(struct list_pid **pids, int pid) +{ + struct list_pid *prev, *free_node, *head = *pids; + char *ip = NULL; + + if (!head) return NULL; + prev = free_node = NULL; + while (head) { + if (head->pid == pid) { + ip = head->ip; + free_node = head; + if (!prev) *pids = head->next; + else prev->next = head->next; + free(free_node); + return ip; + } + prev = head; + head = head->next; + } + return NULL; +} + +// decrement the ref count fora connection, if count reches ZERO then remove the node +static void remove_connection(char *ip) +{ + struct list *head, *prev = NULL, *free_node = NULL; + int hash = haship(ip); + + head = h[hash].head; + while (head) { + if (!strcmp(ip, head->d)) { + head->count--; + free_node = head; + if (!head->count) { + if (!prev) h[hash].head = head->next; + else prev->next = head->next; + free(free_node); + } + break; + } + prev = head; + head = head->next; + } + free(ip); +} + +// Handler function. +static void handle_exit(int sig) +{ + int status; + pid_t pid_n = wait(&status); + + if (pid_n <= 0) return; + char *ip = delete(&pids, pid_n); + if (!ip) return; + remove_connection(ip); + TT.count_all--; + if (toys.optflags & FLAG_v) { + if (WIFEXITED(status)) + xprintf("%s: end %d exit %d\n",toys.which->name, pid_n, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + xprintf("%s: end %d signaled %d\n",toys.which->name, pid_n, WTERMSIG(status)); + if (TT.cn > 1) xprintf("%s: status %d/%d\n",toys.which->name, TT.count_all, TT.cn); + } +} + +// Grab uid and gid +static void get_uidgid(uid_t *uid, gid_t *gid, char *ug) +{ + struct passwd *pass = NULL; + struct group *grp = NULL; + char *user = NULL, *group = NULL; + unsigned int n; + + user = ug; + group = strchr(ug,':'); + if (group) { + *group = '\0'; + group++; + } + if (!(pass = getpwnam(user))) { + n = atolx_range(user, 0, INT_MAX); + if (!(pass = getpwuid(n))) perror_exit("Invalid user '%s'", user); + } + *uid = pass->pw_uid; + *gid = pass->pw_gid; + + if (group) { + if (!(grp = getgrnam(group))) { + n = atolx_range(group, 0, INT_MAX); + if (!(grp = getgrgid(n))) perror_exit("Invalid group '%s'",group); + } + } + if (grp) *gid = grp->gr_gid; +} + +// Bind socket. +static int create_bind_sock(char *host, struct sockaddr *haddr) +{ + struct addrinfo hints, *res = NULL, *rp; + int sockfd, ret, set = 1; + char *ptr; + unsigned long port; + + errno = 0; + port = strtoul(toys.optargs[1], &ptr, 10); + if (errno || port > 65535) + error_exit("Invalid port, Range is [0-65535]"); + if (*ptr) ptr = toys.optargs[1]; + else { + sprintf(toybuf, "%lu", port); + ptr = toybuf; + } + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = ((TT.udp) ?SOCK_DGRAM : SOCK_STREAM); + if ((ret = getaddrinfo(host, ptr, &hints, &res))) + perror_exit("%s", gai_strerror(ret)); + + for (rp = res; rp; rp = rp->ai_next) + if ( (rp->ai_family == AF_INET) || (rp->ai_family == AF_INET6)) break; + + if (!rp) error_exit("Invalid IP %s", host); + + sockfd = xsocket(rp->ai_family, TT.udp ?SOCK_DGRAM :SOCK_STREAM, 0); + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set)); + if (TT.udp) setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &set, sizeof(set)); + if ((bind(sockfd, rp->ai_addr, rp->ai_addrlen)) < 0) perror_exit("Bind failed"); + if(haddr) memcpy(haddr, rp->ai_addr, rp->ai_addrlen); + freeaddrinfo(res); + return sockfd; +} + +static void handle_signal(int sig) +{ + if (toys.optflags & FLAG_v) xprintf("got signal %d, exit\n", sig); + raise(sig); + _exit(sig + 128); //should not reach here +} + +void tcpsvd_main(void) +{ + uid_t uid = 0; + gid_t gid = 0; + pid_t pid; + char haddr[sizeof(struct sockaddr_in6)]; + struct list *head, *newnode; + int hash, fd, newfd, j; + char *ptr = NULL, *addr, *server, buf[sizeof(struct sockaddr_in6)]; + socklen_t len = sizeof(buf); + + TT.udp = (*toys.which->name == 'u'); + if (TT.udp) toys.optflags &= ~FLAG_C; + memset(buf, 0, len); + if (toys.optflags & FLAG_C) { + if ((ptr = strchr(TT.nmsg, ':'))) { + *ptr = '\0'; + ptr++; + } + TT.maxc = atolx_range(TT.nmsg, 1, INT_MAX); + } + + fd = create_bind_sock(toys.optargs[0], (struct sockaddr*)&haddr); + if(toys.optflags & FLAG_u) { + get_uidgid(&uid, &gid, TT.user); + setuid(uid); + setgid(gid); + } + + if (!TT.udp && (listen(fd, TT.bn) < 0)) perror_exit("Listen failed"); + server = sock_to_address((struct sockaddr*)&haddr, NI_NUMERICHOST|NI_NUMERICSERV); + if (toys.optflags & FLAG_v) { + if (toys.optflags & FLAG_u) + xprintf("%s: listening on %s, starting, uid %u, gid %u\n" + ,toys.which->name, server, uid, gid); + else + xprintf("%s: listening on %s, starting\n", toys.which->name, server); + } + for (j = 0; j < HASH_NR; j++) h[j].head = NULL; + sigatexit(handle_signal); + signal(SIGCHLD, handle_exit); + + while (1) { + if (TT.count_all < TT.cn) { + if (TT.udp) { + if(recvfrom(fd, NULL, 0, MSG_PEEK, (struct sockaddr *)buf, &len) < 0) + perror_exit("recvfrom"); + newfd = fd; + } else { + newfd = accept(fd, (struct sockaddr *)buf, &len); + if (newfd < 0) perror_exit("Error on accept"); + } + } else { + sigset_t ss; + sigemptyset(&ss); + sigsuspend(&ss); + continue; + } + TT.count_all++; + addr = sock_to_address((struct sockaddr*)buf, NI_NUMERICHOST); + + hash = haship(addr); + if (toys.optflags & FLAG_C) { + for (head = h[hash].head; head; head = head->next) + if (!strcmp(head->d, addr)) break; + + if (head && head->count >= TT.maxc) { + if (ptr) write(newfd, ptr, strlen(ptr)+1); + close(newfd); + TT.count_all--; + continue; + } + } + + newnode = (struct list*)xzalloc(sizeof(struct list)); + newnode->d = addr; + for (head = h[hash].head; head; head = head->next) { + if (!strcmp(addr, head->d)) { + head->count++; + free(newnode); + break; + } + } + + if (!head) { + newnode->next = h[hash].head; + h[hash].head = newnode; + h[hash].head->count++; + } + + if (!(pid = fork())) { + char *serv = NULL, *clie = NULL; + char *client = sock_to_address((struct sockaddr*)buf, NI_NUMERICHOST | NI_NUMERICSERV); + if (toys.optflags & FLAG_h) { //lookup name + if (toys.optflags & FLAG_l) serv = xstrdup(TT.name); + else serv = sock_to_address((struct sockaddr*)&haddr, 0); + clie = sock_to_address((struct sockaddr*)buf, 0); + } + + if (!(toys.optflags & FLAG_E)) { + setenv("PROTO", TT.udp ?"UDP" :"TCP", 1); + setenv("PROTOLOCALADDR", server, 1); + setenv("PROTOREMOTEADDR", client, 1); + if (toys.optflags & FLAG_h) { + setenv("PROTOLOCALHOST", serv, 1); + setenv("PROTOREMOTEHOST", clie, 1); + } + if (!TT.udp) { + char max_c[32]; + sprintf(max_c, "%d", TT.maxc); + setenv("TCPCONCURRENCY", max_c, 1); //Not valid for udp + } + } + if (toys.optflags & FLAG_v) { + xprintf("%s: start %d %s-%s",toys.which->name, getpid(), server, client); + if (toys.optflags & FLAG_h) xprintf(" (%s-%s)", serv, clie); + xputc('\n'); + if (TT.cn > 1) + xprintf("%s: status %d/%d\n",toys.which->name, TT.count_all, TT.cn); + } + free(client); + if (toys.optflags & FLAG_h) { + free(serv); + free(clie); + } + if (TT.udp && (connect(newfd, (struct sockaddr *)buf, sizeof(buf)) < 0)) + perror_exit("connect"); + + close(0); + close(1); + dup2(newfd, 0); + dup2(newfd, 1); + xexec_optargs(2); //skip IP PORT + } else if(pid > 0) { + insert(&pids, pid, addr); + xclose(newfd); //close and reopen for next client. + if (TT.udp) fd = create_bind_sock(toys.optargs[0], + (struct sockaddr*)&haddr); + } else error_msg(" fork failed"); + } //while(1) +} |