changeset 1138:ecc409de9976 draft

Ashwini Sharma submitted tcpsvd/udpsvd.
author Rob Landley <rob@landley.net>
date Sat, 07 Dec 2013 16:19:17 -0600
parents 5c51c9639a6f
children 2c53be7c9f9b
files toys/pending/tcpsvd.c
diffstat 1 files changed, 403 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toys/pending/tcpsvd.c	Sat Dec 07 16:19:17 2013 -0600
@@ -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)
+}