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++; } }
wow, I’m impressed :-)
excellent writing, even for hobbyists! Thanks for sharing.