Currently, an outbound socket connection (client) can be created using the
syntax:

exec 5<> /dev/tcp/HOST/PORT

This patch implements support for accepting incoming connections (server)
using a slightly different syntax:

exec 6<> /dev/tcp/HOST/:PORT   # note the colon

The listen/accept call will block until a connection is received and it
will be bound to the redirection.

Included in the patch is a small test that has an example of usage. I've
also attach a minimal web server that leverages this functionality (pure
bash except for a call to wc and cat).

Please include me in Cc since I am not on the list.

Thanks,

Joel Martin (kanaka)
From 04df5a7a23325a7e9b42ee3d4b306fa222326d99 Mon Sep 17 00:00:00 2001
From: Joel Martin <git...@martintribe.org>
Date: Tue, 12 Nov 2013 15:30:47 -0500
Subject: [PATCH] Listening socket support via /dev/tcp/HOST/:PORT

In addition to the ability to create normal outgoing socket
connections (/dev/tcp/HOST/PORT) this adds the ability to create
a listen socket connection. The creation of the listener will block
until a new connection is created via the listener.

Signed-off-by: Joel Martin <git...@martintribe.org>
---
 lib/sh/netopen.c              |  110 +++++++++++++++++++++++++++++++----------
 tests/misc/dev-tcp-serv.tests |   44 +++++++++++++++++
 2 files changed, 127 insertions(+), 27 deletions(-)
 create mode 100644 tests/misc/dev-tcp-serv.tests

diff --git a/lib/sh/netopen.c b/lib/sh/netopen.c
index 736d413..0a2a9f1 100644
--- a/lib/sh/netopen.c
+++ b/lib/sh/netopen.c
@@ -69,9 +69,9 @@ extern int inet_aton __P((const char *, struct in_addr *));
 #ifndef HAVE_GETADDRINFO
 static int _getaddr __P((char *, struct in_addr *));
 static int _getserv __P((char *, int, unsigned short *));
-static int _netopen4 __P((char *, char *, int));
+static int _netopen4 __P((char *, char *, int, int));
 #else /* HAVE_GETADDRINFO */
-static int _netopen6 __P((char *, char *, int));
+static int _netopen6 __P((char *, char *, int, int));
 #endif
 
 static int _netopen __P((char *, char *, int));
@@ -154,14 +154,16 @@ _getserv (serv, proto, pp)
  * traditional BSD mechanisms.  Returns the connected socket or -1 on error.
  */
 static int 
-_netopen4(host, serv, typ)
+_netopen4(host, serv, typ, listener)
      char *host, *serv;
-     int typ;
+     int typ, listener;
 {
   struct in_addr ina;
   struct sockaddr_in sin;
   unsigned short p;
-  int s, e;
+  int optval = 1;
+  int s, c, e;
+  const char *step;
 
   if (_getaddr(host, &ina) == 0)
     {
@@ -189,16 +191,39 @@ _netopen4(host, serv, typ)
       return (-1);
     }
 
-  if (connect (s, (struct sockaddr *)&sin, sizeof (sin)) < 0)
+  if (!listener)
+    {
+      step = "connect";
+      if (connect (s, (struct sockaddr *)&sin, sizeof (sin)) < 0)
+        goto cleanup;
+    }
+  else
     {
-      e = errno;
-      sys_error("connect");
+      //internal_error("Listening (4) on: %s:%s %c\n", host, serv, typ);
+      step = "setsockopt";
+      if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval) <0)
+        goto cleanup;
+      step = "bind";
+      if (bind(s, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+        goto cleanup;
+      step = "listen";
+      if (listen(s, 1) < 0)
+        goto cleanup;
+      step = "accept";
+      if ((c = accept(s, (struct sockaddr*)NULL, NULL)) < 0)
+        goto cleanup;
       close(s);
-      errno = e;
-      return (-1);
+      s = c;
     }
 
   return(s);
+
+  cleanup:
+    e = errno;
+    sys_error(step);
+    close(s);
+    errno = e;
+    return (-1);
 }
 #endif /* ! HAVE_GETADDRINFO */
 
@@ -209,13 +234,15 @@ _netopen4(host, serv, typ)
  * on error.
  */
 static int
-_netopen6 (host, serv, typ)
+_netopen6 (host, serv, typ, listener)
      char *host, *serv;
-     int typ;
+     int typ, listener;
 {
-  int s, e;
+  int s, c, e;
   struct addrinfo hints, *res, *res0;
+  int optval = 1;
   int gerr;
+  const char *step;
 
   memset ((char *)&hints, 0, sizeof (hints));
   /* XXX -- if problems with IPv6, set to PF_INET for IPv4 only */
@@ -247,22 +274,45 @@ _netopen6 (host, serv, typ)
 	  freeaddrinfo (res0);
 	  return -1;
 	}
-      if (connect (s, res->ai_addr, res->ai_addrlen) < 0)
+      if (!listener)
 	{
-	  if (res->ai_next)
-	    {
-	      close (s);
-	      continue;
-	    }
-	  e = errno;
-	  sys_error ("connect");
-	  close (s);
-	  freeaddrinfo (res0);
-	  errno = e;
-	  return -1;
+	  step = "connect";
+	  if (connect (s, res->ai_addr, res->ai_addrlen) < 0)
+	    goto cleanup;
+	}
+      else
+	{
+	  //internal_error("Listening 6 on: %s:%s %c\n", host, serv, typ);
+	  step = "setsockopt";
+	  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval) < 0)
+	    goto cleanup;
+	  step = "bind";
+	  if (bind(s, res->ai_addr, res->ai_addrlen) < 0)
+	    goto cleanup;
+	  step = "listen";
+	  if (listen(s, 1) < 0)
+	    goto cleanup;
+	  step = "accept";
+	  if ((c = accept(s, (struct sockaddr*)NULL, NULL)) < 0) 
+	    goto cleanup;
+	  close(s);
+	  s = c;
 	}
       freeaddrinfo (res0);
       break;
+
+      cleanup:
+	if (res->ai_next)
+	  {
+	    close (s);
+	    continue;
+	  }
+	e = errno;
+	sys_error (step);
+	close (s);
+	freeaddrinfo (res0);
+	errno = e;
+	return -1;
     }
   return s;
 }
@@ -278,10 +328,16 @@ _netopen(host, serv, typ)
      char *host, *serv;
      int typ;
 {
+  int listener = 0;
+  if (serv[0] == ':') {
+    listener = 1;
+    serv++;
+  }
+
 #ifdef HAVE_GETADDRINFO
-  return (_netopen6 (host, serv, typ));
+  return (_netopen6 (host, serv, typ, listener));
 #else
-  return (_netopen4 (host, serv, typ));
+  return (_netopen4 (host, serv, typ, listener));
 #endif
 }
 
diff --git a/tests/misc/dev-tcp-serv.tests b/tests/misc/dev-tcp-serv.tests
new file mode 100644
index 0000000..53a89a7
--- /dev/null
+++ b/tests/misc/dev-tcp-serv.tests
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# Start a background server
+(
+CNT=0
+while true; do
+    exec 9<> /dev/tcp/127.0.0.1/:13567 || break
+    echo "CNT: $CNT" >&9
+    CNT=$(( CNT + 1 ))
+    exec 9>&- # close it
+done
+) &
+
+spid=$!
+
+# cleanup the background server on exit
+cleanup() {
+  kill $spid
+}
+trap "cleanup" TERM QUIT INT EXIT
+
+# Wait for the server to startup
+sleep 1
+
+# Connect to the server and get the first response
+exec 10<> /dev/tcp/127.0.0.1/13567
+read -u 10 -r LINE
+exec 10>&-
+echo "Got server response: $LINE"
+if [ "${LINE}" != "CNT: 0" ]; then
+    echo "Expected: CNT: 0"
+    exit 1
+fi
+
+# Connect to the server and get the second response
+exec 10<> /dev/tcp/127.0.0.1/13568
+read -u 10 -r LINE
+echo "Got server response: $LINE"
+if [ "${LINE}" != "CNT: 1" ]; then
+    echo "Expected: CNT: 1"
+    exit 1
+fi
+
+exit 0
-- 
1.7.9.5

Attachment: www.sh
Description: Bourne shell script

Reply via email to