TCPMUX – a mostly overlooked TCP service

TCPMUX is described in RFC-1078 (written some 20 years ago). A reference implementation by Network Wizards can be found at ftp://ftp.nw.com/nw/software/tcpmux.c . It is also implemented in DragonFlyBSD’s inetd, NetBSD’s inetd and FreeBSD’s inetd. OpenBSD does not support for it.

The Protocol

A TCP client connects to a foreign host on TCP port 1. It sends the service name followed by a carriage-return line-feed . The service name is never case sensitive. The server replies with a single character indicating positive (“+”) or negative (“-“) acknowledgment, immediately followed by an optional message of explanation, terminated with a . If the reply was positive, the selected protocol begins; otherwise the connection is closed.

The 15+ years I have been a sysadmin I have never seen anyone making a use of it, which is a pity: Most of the time I see fellow sysadmins who want to write a custom daemon, either write it as a standalone server (usually starting with passivesock.c or passiveTCP.c from Comer’s Internetworking with TCP/IP vol.3), or writing is as a simple stdin/stdout application that is started via inetd. The most trivial problem is sometimes more than trivial:

– What port will this application run on?

It seems that 65535 ports is a lot of freedom to choose from and most people want to use “interesting” port numbers (for any definition of interesting). Add firewall policies and router access lists in the picture, you can have a non-technical deadlock in no time!

TCPMUX might be a choice to help simplify / avoid such situations. Any service that supports TCPMUX listens on port 1/tcp and can be forked by inetd(8) (either internally or externally with the help of a tiny server). After all, it can be considered as an “inetd inside inetd” (the classic inetd responding to requests on a port, TCPMUX responding to requests based on the name of the service) and even if you do not want to use TCPMUX, a similar (homegrown) solution might be the answer to keeping your packet filters lean and less complex. It does not have to be less complex than it has to be though. The Wikipedia article on tcpmux clearly identifies risks that come with deploying it. Personally, I view tcpmux as an old and simple TCP RPC mechanism.

Appendix: tcpmux.c

Since the Network Wizards site seems to be down / taken over by some other entity, here is the original tcpmux daemon code (also at github https://github.com/a-yiorgos/tcpmux ):


/*
   TCPMUX.C
   by Mark K. Lottor
   November 1988

  This program implements RFC 1078.
  The program runs under inetd, on TCP port 1 (TCPMUX).
  Don't forget to make the necessary mods to '/etc/services'.
  When a connection is made from a foreign host, the service 
  requested is sent to us, we look it up in the config file,
  and exec the proper server for the service.  This program
  returns a negative reply if the server doesn't exist, otherwise
  the invoked server is expected to return the positive reply (see
  note below).

  The format of the config file is a seperate line for each service,
  or a line starting with "#" for comments.  The service lines are
  the name of the service, some whitspace, and the filename to exec
  for the service.  The program is passed the tcp connection as
  file descriptors 0 and 1.

  If the service name in the config file is immediately preceded
  by a '+', the server will return the positive reply for the
  process; this is for compatability with older server code,
  and also allows you to invoke programs that use stdin/stdout
  without putting any special server code in them.

*/

#include <stdio.h>
#include <ctype.h>

#define CONFIG_FILE "/usr/local/etc/tcpmux.cf"
#define MAX_LINE 120
#define MAXNAMLEN 64


main()
{
  FILE *cfd;
  char service[MAX_LINE];
  char linbuf[MAX_LINE];
  char sname[64], sfile[MAXNAMLEN];
  char *p;

/*
 inetd passes us the tcp connection as fd[0].
*/

  dup2(0,1);      /* make tcp connection be stdin and stdout */

/* get service name requested */
  if (fgets(service, MAX_LINE, stdin) == NULL)
  {
    puts("-Error reading service name.\r");
    fflush(stdout);
    exit(0);
  }
/* kill trailing newline */
  p = service;
  while ((*p != '\0') && (*p != '\r') && (*p != '\n')) p++;
  *p = '\0';   

/* open config file */
  if (!(cfd = fopen(CONFIG_FILE,"r")))
  {
    puts("-No services available\r");
    fflush(stdout);
    exit(0);
  }


/* help is a required command, and lists available services one per line */
  if (!strCMP(service,"help"))
  {
    while (fgets(linbuf, MAX_LINE, cfd) != NULL)
    {
      if (*linbuf != '#')              /* if its not a comment */
        if (sscanf(linbuf,"%s %s",sname,sfile) == 2)
        {
          p = sname; if (*p == '+') p++;
          printf("%s\r\n",p);          /* then display service name */
        }
    }
    fflush(stdout);
    fclose(cfd);
    exit(0);
  }

/* try matching a line of config file with service requested */
  while (fgets(linbuf, MAX_LINE, cfd) != NULL)
  {
    if (*linbuf != '#')              /* if its not a comment */
      if (sscanf(linbuf,"%s %s",sname,sfile) == 2)
      {
        p = sname; if (*p == '+') p++;
        if (!strCMP(service,p))
        {
          fclose(cfd);
          if (*sname == '+') puts("+Go\r"); fflush(stdout);
          execl(sfile,p,0)    ;      /* found it -- so start it */
        }
      }
  }
  puts("-Service not available\r");
  fflush(stdout);
  exit(0);
}


/* a proper string compare */
strCMP(s1,s2)
  char *s1, *s2;
{
  register int c1, c2;

  for (;;) {
    c1 = (isupper(*s1) ? tolower(*s1) : *s1);
    c2 = (isupper(*s2) ? tolower(*s2) : *s2);
    if (c1 != c2)
      return c1 - c2;
    if (c1 == '\0')
      return 0;
    s1++;
    s2++;
  }
}

One thought on “TCPMUX – a mostly overlooked TCP service

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s