TCP/IP over TTY lines (source)

Marc Elvy elvy at harvard.UUCP
Sat Feb 25 07:34:09 AEST 1984


Since I submitted an article some time ago asking for advice on how to
implement TCP/IP through TTY ports, I received many letters -- some
offering advice and some wanting to know what I discovered.  Rather
than respond to each of them in person, I am submitting here the sources
necessary to effect such a system.

One of our systems hackers (Robert Morris, harvard!morris) fixed up
the kernel on our 4.2 Vaxes so that they talked to one another using
TCP/IP stuff (we can rlogin, rwho, ruptime, rsh, telnet, ftp, etc.).
It is a very nice setup, considering we have two Vaxes right next to
one another, yet no Ethernet equipment.  While (in empirical terms) it
is rather slow, it is quite usable if there are not too many people
on the system.

What follows are four files:  README, if_itt.c, ittconf.c, tty_conf.c
The README file contains a description of how to modify the kernel
to run TCP/IP over tty lines on 4.2BSD systems.  The files are separated
by:
****************************
	filename
****************************
If you have any questions, send mail to me (or directly to harvard!morris).

Enjoy.

Marc

Enc.
-----------------

****************************
	README
****************************

These are the changes needed to install tcp over tty lines on a 4.2
system.

Put if_itt.c in vaxif/if_itt.c
Add a line in conf/files reading
vaxif/if_itt.c		optional itt inet
and config SYSTEM; cd ../SYSTEM; make depend

If you have made no changes to sys/tty_conf.c, just copy the one
supplied. Otherwise, add a line discipline (I assume it is number
5) which uses ittopen, ittclose, and ittrint.

Add lines in sys/init_main.c reading
#include "itt.h"
#if NITT > 0
	ittattach();
#endif
after the lines reading (roughly)
#if NLOOP > 0
	loattach();
#endif

cd ../SYSTEM ; make vmunix

(If it doesn't work, send me a note.)

Compile ittconf.c and install it someplace; it tells the itt driver
which tty line corresponds to which host number.

Add lines to /etc/rc.local for each non-local host reading
ittconf /dev/ttyxx <host> > /dev/console &
(use some appropriate pathname for ittconf, like /etc/ittconf)
where /dev/ttyxx is the tty line to the other host, and <host> is the
host number you have chosen (like 1, not 126.1).
ittconf opens the indicated device, changes the line discipline
to number five, and does an ioctl whose number is the host number
at the other end of the line, and then pause()s to keep the line
open and the line discipline set. If you kill it, you'll stop any
communication on the line and flush the packet queues for that line.


Right at the beginning of /etc/rc.local, add
/etc/ifconfig itt0 <net>.<localhost>
where <net> is the net number you have chosen (126 here), and <localhost>
is the host number on that net you have chosen for this host. One one vax
we have
/etc/ifconfig itt0 126.1
and on the other
/etc/ifconfig itt0 126.2
So our vaxes are hosts 126.1 and 126.2; you must add entries in /etc/hosts
also, of the form
vax1		126.1
vax2		126.2
...
The additions should be the same on all machines; they should all know
their local addresses as well as the addresses of other hosts.
The ifconfig lines must be AFTER the iffconf lines.
It should work. It has a few problems. It's pretty slow, but it's set
up so that if you have more than one line between two hosts (configured
by more than one ittconf command), it shares the load between them. But
for various reasons, it often hangs for a fraction of a second about every
1000 characters under full load. This is because I had to use timeouts
instead of wakeups to keep the tty output queues full, and the timeouts
have granularity problems. It's not been very thouroughly
tested. If you check back in a while I'll have
packet forwarding set up. (Ie, right now if two machines aren't directly
connected, they can't talk. The driver should know how to pass on packets
for a third party.)
****************************
	if_itt.c
****************************

/*	if_itt.c	6.1	83/07/29	*/

#include "../machine/pte.h"

#include "../h/param.h"
#include "../h/systm.h"
#include "../h/mbuf.h"
#include "../h/buf.h"
#include "../h/protosw.h"
#include "../h/socket.h"
#include "../h/vmmac.h"
#include "../h/ioctl.h"
#include "../h/errno.h"

#include "../net/if.h"
#include "../net/netisr.h"
#include "../net/route.h"
#include "../netinet/in.h"
#include "../netinet/in_systm.h"
#include "../netinet/ip.h"
#include "../netinet/ip_var.h"

#include "../vax/cpu.h"
#include "../vax/mtpr.h"
#include "../vaxif/if_uba.h"
#include "../vaxuba/ubareg.h"
#include "../vaxuba/ubavar.h"
#include "../h/tty.h"

/* don't increase this */
#define	ITTMTU		350	/* Max transmission unit (bytes) */
#define ITTHIWAT	1000	/* tty total output usage */
#define ITTSLOWT	((ITTHIWAT * 60) / 900)
#define ITTFASTT	(ITTSLOWT / 3)

/* escape codes for synchronization */
/* send ESC, ESC???, argument */
#define ESC 'E'
#define ESCBEGIN 'b'	/* null argument */
#define ESCEND 'e'	/* null argument */
#define ESCDEST 'd'	/* argument is destination host # */
#define ESCHOP 'h'	/* hop count, discard after a while */

/* state codes */
#define IDLE 0
#define DATA 1
/* other codes are escape codes; if state is ESCDEST, waiting for dest */

int	ittattach(), ittrint();
int	ittinit(), ittioctl(), ittoutput(), ittreset();
int	ittwatch();


struct ifnet ittif;
int itterrIDLE, itterrBEGIN, itterrEND;
extern struct tty *ittdesttotp();
struct itt_header{
	unsigned int itt_dest;
};
#define NITTL 5
struct ittl {
	int itt_state;
	struct tty *itt_tp;
	int itt_host;
	struct mbuf *itt_mbase, *itt_m;
	unsigned int itt_dest, itt_hop;
} ittl[NITTL];

/*
 * Interface exists: make available by filling in network interface
 * record.  System will initialize the interface when it is ready
 * to accept packets.
 */
ittattach()
{
	register struct ifnet *ifp = &ittif;

	ifp->if_name = "itt";
	ifp->if_mtu = ITTMTU;
	ifp->if_init = ittinit;
	ifp->if_output = ittoutput;
	ifp->if_ioctl = ittioctl;
	ifp->if_reset = ittreset;
	if_attach(ifp);
	timeout(ittwatch, (caddr_t) 0, 60);
}

/*
 * Reset of interface after UNIBUS reset.
 * If interface is on specified uba, reset its state.
 */
ittreset()
{

	ittinit();
}

/*
 * Initialization of interface; clear recorded pending
 * operations, and reinitialize UNIBUS usage.
 */
ittinit()
{
	struct sockaddr_in *sin;
	int s;

	sin = (struct sockaddr_in *)&ittif.if_addr;
	if (sin->sin_addr.s_addr == 0)
		return;
	ittif.if_flags |= IFF_UP | IFF_RUNNING;
	if_rtinit(&ittif, RTF_UP);
}

/*
 * ITT output routine.
 */
ittoutput(ifp, m, dst)
	struct ifnet *ifp;
	struct mbuf *m;
	struct sockaddr *dst;
{
	struct tty *tp;
	int s, error, i;
	unsigned int dest;
	struct mbuf *m2;
	struct itt_header *ittp;

	switch (dst->sa_family) {

#ifdef INET
	case AF_INET:
		dest = in_lnaof(((struct sockaddr_in *)dst)->sin_addr);
		break;
#endif
	default:
		printf("itt%d: can't handle af%d\n", ifp->if_unit,
			dst->sa_family);
		error = EAFNOSUPPORT;
		goto bad;
	}
	/* insert a fake header packet with destination in it;
	 * removed and examined by ittstart().
	 */
	if (m->m_off > MMAXOFF ||
	    MMINOFF + sizeof (struct itt_header) > m->m_off) {
		m2 = m_get(M_DONTWAIT, MT_HEADER);
		if (m2 == 0) {
			printf("no bufs in output\n");
			error = ENOBUFS;
			goto bad;
		}
		m2->m_next = m;
		m2->m_off = MMINOFF;
		m2->m_len = sizeof (struct itt_header);
		m = m2;
	} else {
		m->m_off -= sizeof (struct itt_header);
		m->m_len += sizeof (struct itt_header);
	}
	ittp = mtod(m, struct itt_header *);
	ittp->itt_dest = dest;

	tp = ittdesttotp(dest);
	if(tp == NULL && dest){
		m_freem(m);
		return(EHOSTUNREACH);
	}
	s = splimp();
	if(dest == 0){
		for(i = 0; i < 20; i++){
			if((tp = ittdesttotp(i)) != NULL){
				m2 = m_copy(m, 0, M_COPYALL);
				mtod(m2, struct itt_header *)->itt_dest = i;
				if(IF_QFULL(&ifp->if_snd)){
					IF_DROP(&ifp->if_snd);
					error = ENOBUFS;
					splx(s);
					m_freem(m);
					m_freem(m2);
					return(error);
				}
				IF_ENQUEUE(&ifp->if_snd, m2);
			}
		}
		m_freem(m);
	} else {
		if(IF_QFULL(&ifp->if_snd)){
			IF_DROP(&ifp->if_snd);
			ifp->if_oerrors++;
			error = ENOBUFS;
			splx(s);
			m_freem(m);
			return(error);
		}
		IF_ENQUEUE(&ifp->if_snd, m);
	}
	while(ittoutq() < ITTHIWAT && ittstart())
		;
	splx(s);
	return (0);
bad:
	m_freem(m);
	return(error);
}

/*
 * Start or restart output on interface.
 */
ittstart()
{
	u_char c;
	int len;
	u_char *cp;
	int dest, s;
	register struct mbuf *m, *mm;
	struct mbuf *m2;
	struct tty *tp;

	s = splimp();
	IF_DEQUEUE(&ittif.if_snd, m);
	if(m == 0){
		splx(s);
		return(0);
	}
	mm = m;
	dest = mtod(m, struct itt_header *)->itt_dest;
	m->m_off += sizeof(struct itt_header);
	m->m_len -= sizeof(struct itt_header);

	tp = ittdesttotp(dest);
	if(tp == NULL){
		printf("itt dq %d?\n", dest);
		m_freem(mm);
		splx(s);
		return(1);
	}
	ittsendesc(tp, ESCBEGIN, 0);
	ittsendesc(tp, ESCDEST, dest);

	while(m){
		for(cp = mtod(m, u_char *); cp < mtod(m, u_char *) + m->m_len; cp++){
			if(*cp == ESC) ittsend(tp, ESC);
			ittsend(tp, *cp);
		}
		MFREE(m, m2);
		m = m2;
	}
	m = mm;
	ittsendesc(tp, ESCEND, 0);
	ittif.if_opackets++;
	ttstart(tp);
	splx(s);
	return(1);
}

ittrint(c, tp)
register struct tty *tp;
u_char c;
{
	int i, s;
	extern int tk_nin;
	struct ifqueue *ipq;
	struct ittl *ip;

	tk_nin++;
	for(i = 0; i < NITTL; i++){
		if(ittl[i].itt_tp == tp) break;
	}
	if(i >= NITTL){
		printf("ittrint %x?\n", tp);
		splx(s); return;
	}
	ip = &ittl[i];
	s = splimp();
	switch(ip->itt_state){
	case IDLE:
		if(c == ESC){
			ip->itt_state = ESC;
			splx(s); return;
		}
		itterrIDLE++;
		ittif.if_ierrors++;
		splx(s); return;
	case DATA:
		if(c == ESC){
			ip->itt_state = ESC;
			splx(s); return;
		}
		break;
	case ESC:
		if(c == ESC){
			/* escaped ESC */
			ip->itt_state = DATA;
			break;
		}
		ip->itt_state = c;
		splx(s); return;
	case ESCBEGIN:
		ip->itt_state = DATA;
		if(ip->itt_mbase){
			ittif.if_ierrors++;
			itterrBEGIN++;
			m_freem(ip->itt_mbase);
			ip->itt_mbase = ip->itt_m = 0;
		}
		ip->itt_dest = 0;
		ip->itt_hop = 0;
		splx(s); return;
	case ESCEND:
		if(ip->itt_mbase == NULL){
			itterrEND++;
			ittif.if_ierrors++;
			splx(s); return;
		}
		if(ip->itt_hop > 20){
			printf("itt: loop\n");
			m_freem(ip->itt_mbase);
			ip->itt_mbase = ip->itt_m = 0;
			splx(s);
			ittif.if_ierrors++;
			return;
		}
		ipq = &ipintrq;
		if(IF_QFULL(ipq)){
			IF_DROP(ipq);
			ittif.if_collisions++;
			m_freem(ip->itt_mbase);
			ip->itt_mbase = ip->itt_m = 0;
		} else {
			if(mbad(ip->itt_mbase)) panic("ittrint: mbad enq\n");
			IF_ENQUEUE(ipq, ip->itt_mbase);
			schednetisr(NETISR_IP);
			ittif.if_ipackets++;
		}
		ip->itt_state = IDLE;
		ip->itt_m = ip->itt_mbase = 0;
		splx(s); return;
	case ESCHOP:
		ip->itt_hop = c;
		splx(s); return;
	case ESCDEST:
		ip->itt_dest = c;
		ip->itt_state = DATA;
		splx(s); return;
	default:
		printf("ittrint: unk state 0%o\n", ip->itt_state);
		ip->itt_state = 0;
		splx(s); return;
	}
	if(ip->itt_mbase == NULL){
		ip->itt_m = ip->itt_mbase = m_get(M_DONTWAIT, MT_DATA);
		if(ip->itt_m == 0){
			splx(s);
			return;
		}
		ip->itt_m->m_len = 1;
		ip->itt_m->m_dat[0] = c;
	} else if((ip->itt_m->m_len + ip->itt_m->m_off) >= MMAXOFF){
		ip->itt_m->m_next = m_get(M_DONTWAIT, MT_DATA);
		if(ip->itt_m->m_next == 0){
			splx(s);
			return;
		}
		ip->itt_m = ip->itt_m->m_next;
		ip->itt_m->m_len = 1;
		ip->itt_m->m_dat[0] = c;
	} else {
		ip->itt_m->m_dat[ip->itt_m->m_len++] = c;
		if(mbad(ip->itt_mbase)) panic("ittrint: after else mbad\n");
	}
	splx(s);
}


/*
 * Process an ioctl request.
 */
ittioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	int cmd;
	caddr_t data;
{
	struct ifreq *ifr = (struct ifreq *)data;
	struct sockaddr_in *sin;
	int s = splimp(), error = 0;

	switch (cmd) {

	case SIOCSIFADDR:
		if (ifp->if_flags & IFF_RUNNING)
			if_rtinit(ifp, -1);	/* delete previous route */
		sin = (struct sockaddr_in *)&ifr->ifr_addr;
		ifp->if_addr = *(struct sockaddr *)sin;
		ifp->if_net = in_netof(sin->sin_addr);
		ifp->if_host[0] = in_lnaof(sin->sin_addr);
		ifp->if_broadaddr = *(struct sockaddr *)sin;
		sin = (struct sockaddr_in *)&ifp->if_broadaddr;
		sin->sin_addr = if_makeaddr(ifp->if_net, INADDR_ANY);
		ifp->if_flags |= IFF_BROADCAST;
		if (ifp->if_flags & IFF_RUNNING)
			if_rtinit(ifp, RTF_UP);
		else
			ittinit(ifp->if_unit);
		break;

	default:
		error = EINVAL;
	}
	splx(s);
	return (error);
}

extern int lbolt;

ittsend(tp, c)
struct tty *tp;
u_char c;
{
	int s;

	if(putc(c, &tp->t_outq)){
		printf("ittsend: out of clist\n");
		return;
	}
}

ittopen(dev, tp)
dev_t dev;
register struct tty *tp;
{
	return(0);
}

ittclose(tp)
register struct tty *tp;
{
	int i;
	for(i = 0; i < NITTL; i++){
		if(ittl[i].itt_tp == tp) ittl[i].itt_tp = NULL;
	}
	return(0);
}

itttioctl(tp, cmd, addr)
struct tty *tp;
caddr_t addr;
{
	int i, s;

	for(i = 0; i < NITTL; i++){
		if(ittl[i].itt_tp == 0){
			break;
		}
	}
	if(i >= NITTL){
		return(ENXIO);
	}
	ittl[i].itt_tp = tp;
	ittl[i].itt_host = cmd;
	ittl[i].itt_state = 0;
	return(0);
}

ittwatch()
{
	while(ittoutq() < ITTHIWAT && ittstart())
		;
	if(ittoutq() >= ITTHIWAT){
		timeout(ittwatch, (caddr_t) 0, ITTFASTT);
	} else {
		timeout(ittwatch, (caddr_t) 0, ITTSLOWT);
	}
}

ittoutq()
{
	int i, cc = 0;

	for(i = 0; i < NITTL; i++){
		if(ittl[i].itt_tp){
			cc += ittl[i].itt_tp->t_outq.c_cc;
		}
	}
	return(cc);
}

mbad(m)
struct mbuf *m;
{
	while(m){
		switch(m->m_type){
		case MT_DATA:
		case MT_HEADER:
		case MT_FTABLE:
			break;
		default:
			printf("mbad %d\n", m->m_type);
			return(1);
		}
		if((m->m_off + m->m_len) > MMAXOFF || (m->m_off < MMINOFF)){
			printf("mbad off/size: off %d len %d\n", m->m_off, m->m_len);
			return(1);
		}
		m = m->m_next;
	}
	return(0);
}

struct tty *
ittdesttotp(dest)
{
	int i, mincc = 9999;
	struct tty *tp = NULL;

	for(i = 0; i < NITTL; i++){
		if(ittl[i].itt_tp && ittl[i].itt_host == dest){
			if(ittl[i].itt_tp->t_outq.c_cc < mincc){
				tp = ittl[i].itt_tp;
				mincc = tp->t_outq.c_cc;
			}
		}
	}
	return(tp);
}

ittsendesc(tp, c, a)
struct tty *tp;
u_char c, a;
{
	ittsend(tp, ESC);
	ittsend(tp, c);
	ittsend(tp, a);
}
****************************
	ittconf.c
****************************

/* ittconf.c */

#include <sgtty.h>

main(argc, argv)
char *argv[];
{
	int ldisc, host = 1;
	int speed = B9600;
	struct sgttyb vec;
	int fd;

	if(argc < 2){
		printf("Usage: %s dev\n", argv[0]);
		exit(1);
	}
	if(argc > 2) host = atoi(argv[2]);
	if(argc > 3){
		if(strcmp(argv[3], "300") == 0) speed = B300;
		if(strcmp(argv[3], "1200") == 0) speed = B1200;
		if(strcmp(argv[3], "2400") == 0) speed = B2400;
	}
	if((fd = open(argv[1], 2)) < 0){
		perror(argv[1]);
		exit(1);
	}
	ldisc = 5;
	vec.sg_ispeed = vec.sg_ospeed = speed;
	vec.sg_flags = RAW | EVENP | ODDP;
	stty(fd, &vec);
	if(ioctl(fd, TIOCSETD, &ldisc) < 0){
		perror("SETD");
		exit(1);
	}
	if(ioctl(fd, host, 0) < 0){
		perror("host");
	}
	printf("yup\n");
	pause();
}
****************************
	tty_conf.c
****************************

/*	tty_conf.c	6.2	83/09/25	*/

#include "../h/param.h"
#include "../h/systm.h"
#include "../h/buf.h"
#include "../h/tty.h"
#include "../h/conf.h"

int	nodev();
int	nulldev();

int	ttyopen(),ttyclose(),ttread(),ttwrite(),nullioctl(),ttstart();
int	ttyinput();

#include "bk.h"
#include "itt.h"
#if NBK > 0
int	bkopen(),bkclose(),bkread(),bkinput(),bkioctl();
#endif

#include "tb.h"
#if NTB > 0
int	tbopen(),tbclose(),tbread(),tbinput(),tbioctl();
#endif

#if NITT > 0
int	ittopen(), ittclose(), itttioctl(), ittrint();
#endif

struct	linesw linesw[] =
{
	ttyopen, nulldev, ttread, ttwrite, nullioctl,
	ttyinput, nodev, nulldev, ttstart, nulldev,
#if NBK > 0
	bkopen, bkclose, bkread, ttwrite, bkioctl,
	bkinput, nodev, nulldev, ttstart, nulldev,
#else
	nodev, nodev, nodev, nodev, nodev,
	nodev, nodev, nodev, nodev, nodev,
#endif
	ttyopen, ttyclose, ttread, ttwrite, nullioctl,
	ttyinput, nodev, nulldev, ttstart, nulldev,
#if NTB > 0
	tbopen, tbclose, tbread, nodev, tbioctl,
	tbinput, nodev, nulldev, ttstart, nulldev,		/* 3 */
#else
	nodev, nodev, nodev, nodev, nodev,
	nodev, nodev, nodev, nodev, nodev,
#endif
#if NTB > 0
	tbopen, tbclose, tbread, nodev, tbioctl,
	tbinput, nodev, nulldev, ttstart, nulldev,		/* 4 */
#else
	nodev, nodev, nodev, nodev, nodev,
	nodev, nodev, nodev, nodev, nodev,
#endif
#if NITT > 0
	ittopen, ittclose, nodev, nodev, itttioctl,
	ittrint, nodev, nodev, ttstart, nulldev,		/* 5 */
#else
	nodev, nodev, nodev, nodev, nodev,
	nodev, nodev, nodev, nodev, nodev,
#endif
};

int	nldisp = sizeof (linesw) / sizeof (linesw[0]);

/*
 * Do nothing specific version of line
 * discipline specific ioctl command.
 */
/*ARGSUSED*/
nullioctl(tp, cmd, data, flags)
	struct tty *tp;
	char *data;
	int flags;
{

#ifdef lint
	tp = tp; data = data; flags = flags;
#endif
	return (-1);
}



More information about the Comp.sources.unix mailing list