Path: seismo!harvard!husc6!panda!talcott!topaz!hedrick (Charles Hedrick) From: sources-request@panda.UUCP Newsgroups: mod.sources Subject: telnetd in the kernel Message-ID: <1788@panda.UUCP> Date: 4 May 86 01:18:46 GMT Sender: jpn@panda.UUCP Lines: 1537 Approved: jpn@panda.UUCP Mod.sources: Volume 4, Issue 90 Submitted by: talcott!topaz!hedrick (Charles Hedrick) [ I haven't tried any of this - you're on your own - mod ] This is a modified version of the posting a couple of weeks ago by nyit!rick. The original one moved the performance-critical part of rlogin into the kernel. My version handles both rlogin and telnet. Note however, that it handles only the server part. In our environment, many users have lines connected to terminal servers that speak Telnet. We would like to be able to have a machine all of whose users are coming in via Telnet. I am much less concerned about outgoing Telnet. (After all, we can just hit BREAK and open another sesssion. Why run Telnet on a host?) Tests on a Pyramid 90X suggest that typing a file at 9600 baud via telnet uses a large fraction of the CPU. Not all of that shows up in ps, but in vmstat, some extra system overhead show up. Not only does telnet eat your CPU, but it is also more sensitive to load than it might be. That is, under heavy load, you not only have to worry about your Emacs getting paged out. Your telnetd will also get delayed. By moving telnetd into the kernel, the CPU usage is dramatically decreased. I haven't done any really careful tests, but it appears that typing a file at 9600 baud uses 20% of the CPU briefly while buffers get filled, and less than 10% thereafter. (I can't be sure. It could be as low as 5%.) More importantly, it makes your connection feel like a local terminal, assuming that your telnet user process is not adding any delay. Rick's original code was quite simple, because rlogin is quite simple. rlogind simply does a magic IOCTL which causes the kernel to crosspatch the incoming network connection to the pty. It stays that way until the user logs out or the connection goes away. Unfortunately, it is not practical to implement Telnet entirely in the kernel. Telnet has things called "negotiations". These allow one end to ask the other to turn off echoing, go into binary mode, and any of a number of wierder options. These, and a few other Telnet commands, are all preceeded by an IAC character (decimal 255). If you actually type an IAC character on your terminal, your telnet process will send two IAC's, so that it doesn't look like a negotiation. This means that telnetd must scan the network for IAC's. When it finds one, it must interpret the resulting command. It must also scan the terminal output from the pty. If it finds any IAC's, it must add an extra IAC, so that the result is seen by the other end as an IAC character, rather than a command. It is reasonable to do the output processing in the kernel. But it is not reasonable to do the input processing. So when my code sees an IAC in incoming data, it simply breaks the link and returns from the IOCTL. The daemon reads the command, does whatever it needs to do, and then returns to the kernel with the IOCTL. The IOCTL used to pass fd's for the network and pty to be crosspatched. I now add 8-bit modes in the high-order end of the argument, one for the input side and one for the output side. 0 gives you rlogin. 1 gives you normal telnet. 2 gives you telnet binary (which currently is implemented only for input, just as in 4.2 telnetd). Some caveats about this code: 1) the kernel code is well tested. So is rlogind. telnetd is not well tested. It works with Unix, TOPS-20, and Bridge terminal servers. However none of those things is able to generate certain of the telnet commands. For example, there is a Telnet protocol command to delete the previous character. Rather than send a rubout character, your telnet user process can send a system-independent delete character telnet command. None of the systems we use do this. We all just hit rubout, or whatever your delete character is currently set to. If there is a bug in this code, it should be a fairly simple typo, and be easy enough to fix. 2) the telnetd code will work only with the modified kernel. Rick's original rlogin was designed so it reverts to normal code if you run it on a kernel without his patches. The changes to telnetd were more extensive, so I did not do it. Presumably my version of telnetd will coredump if run on kernel without these patches. 3) the kernel code is intended to be functionally identical to the telnetd on which it is modelled. In particular, I have not fixed the bug involving bare CR's. (According to the telnet spec, if CR is not followed by LF, the server is required to insert a NUL after the CR. 4.2 Telnet does not do this. Neither does my code. All of the Telnet user programs that we know about can handle CR's followed by other things. Implementing this would have taken more time than I had to devote to the project.) 4) this code has been tested only on a Pyramid 90X. It should work unchanged with any release 3.0 Pyramid single-processor system. The changes for a VAX should be fairly simple. Actually, the only one I know of is in nvs_output. On the Pyramid, you output something to the network by asking the network code for some space, using sbnext. This returns you a pointer. You copy your data to the place pointed to. You then do tcp_usrreq(PRU_SEND .... Apparently, on the VAX, things work a bit differently. You allocate an mbuf, copy the data into the mbuf, and then pass the mbuf as an argument to tcp_usrreq. There are comments in nvs_output that suggest what the code should probably look like, but I make no guarantees. To find it, search for the string "VAX" in tty_nvs.c. I believe that Pyramid operating system releases older than 3.0 should be treated like a VAX. To be sure, find the code in tcp_usrreq that implements PRU_SEND. (Just search for "case PRU_SEND" in ../netinet/tcp_usrreq.c.) If that case contains a call to sbappend, then treat it like a VAX.) 5) This code has not been symmetrized. It will not run on a Pyramid dual-processor system. I think I know how to symmetrize it. But since I don't (yet) have a dual-processor machine, I have no way to test it. 6) in tty_nvs.c, set the flag NFS depending upon whether you have Sun's NVS in your kernel. Or if your compilation process defines NFS externally, just remove the #define. Even if you are not using telnet, you might want to look at this code. I have improved rick's code in two major ways: 1) the original code used "busy waits" in the output code. That is, when the network had enough data, the code would dismiss for N milliseconds. Every N msec it would check to see if it was ready for more data. I have added a hook into sbwakeup, so that the code gets awakened when there is more space. It doesn't have to keep reactivating and checking. 2) the original code has a table which gives the associations between pty's and network connections. If you have a network connection and need to find the pty it goes to, it does a linear search of that table. I have added a short to the socket buffer, and put the table index into it. This eliminates the search. If you are not using a Pyramid, make sure that this change to socketvar.h isn't going to change the struct in some disasterous way. It happened that on the Pyramid, there was a free short in the place where I added that field. If your compiler allocates fields differently, it may not be free for you. The rest of this posting consists of two pieces: a bunch of diffs to existing files, and a new file, tty_nvs.c. If you have a Pyramid, you should modify .../conf/makefromsource (a simple makefile) to cause tty_nvs.c to be compiled and linked into the kernel. I have no idea what you do on other machines. *** ../conf/conf.c.HOLD Thu Feb 6 00:13:04 1986 --- ../conf/conf.c Sun Apr 20 01:24:41 1986 *************** *** 354,359 int ptcopen(),ptcclose(),ptcread(),ptcwrite(),ptcselect(); int ptyioctl(), ptsselect(); struct tty pt_tty[]; #else #define ptsopen nodev #define ptsclose nodev --- 354,361 ----- int ptcopen(),ptcclose(),ptcread(),ptcwrite(),ptcselect(); int ptyioctl(), ptsselect(); struct tty pt_tty[]; + struct socket *ptynvsso[NPTY]; + int ptyoutmode[NPTY]; #else #define ptsopen nodev #define ptsclose nodev *** tty_pty.c.HOLD Tue Aug 27 01:46:18 1985 --- tty_pty.c Sun Apr 20 04:45:42 1986 *************** *** 26,31 extern int npty; /* declared in conf.c */ extern struct tty pt_tty[]; /* NOTE: the following declaration is also make in conf.c Keep the two in sync (should be put in a .h file somewhere) */ --- 26,35 ----- extern int npty; /* declared in conf.c */ extern struct tty pt_tty[]; + #include "../h/socketvar.h" + extern struct socket *ptynvsso[]; /* socket indexed by pty minor dev# */ + /* decl in conf.c, so no dependence on NPTYS here */ + /* NOTE: the following declaration is also make in conf.c Keep the two in sync (should be put in a .h file somewhere) */ *************** *** 787,792 } return (error); } /* * select routine for slave -- handles PF_REMOTE modes and PF_EOT states. --- 791,837 ----- } return (error); } + + /* + * Transfer characters from NVS tty outq to associated socket + * (Simply a jacket because timeout() accepts only 1 argument). + * + * Called at or below network IPL + */ + ptsnvsnet(tp) + struct tty *tp; + { + nvs_output(tp, &pt_ioctl[minor(tp->t_dev)].pt_send); + } + + /* + * Alternate t_oproc routine used when the pty is operating as an NVS + */ + ptsnvsstart(tp) + register struct tty *tp; + { + register int s; + + s = spl5(); + /* + * TS_BUSY set means Ptsnvsnet() is either running or queued to run + */ + if ((tp->t_state & TS_BUSY) == 0) { + tp->t_state |= TS_BUSY; + /* + * If at low IPL, can make output happen now; + * otherwise must sequence it through softclock + */ + if (s == 0) { + (void) spl0(); /* reduce IPL */ + nvs_output(tp, &pt_ioctl[minor(tp->t_dev)].pt_send); + } + else + timeout(ptsnvsnet, (caddr_t)tp, 0); + } + splx(s); + } + /* * select routine for slave -- handles PF_REMOTE modes and PF_EOT states. *** uipc_socket2.c.HOLD Tue Dec 10 19:57:26 1985 --- uipc_socket2.c Sun Apr 20 23:58:22 1986 *************** *** 326,331 register struct sockbuf *sb; { if (sb->sb_sel) { selwakeup(sb->sb_sel, sb->sb_flags & SB_COLL); sb->sb_sel = 0; --- 326,340 ----- register struct sockbuf *sb; { + if (sb->sb_flags & SB_NVS) { + nvs_input(sb); + return; + } + if (sb->sb_flags & SB_NVS_WAIT) { + if (sbspace(sb) >= sb->sb_lowat) + nvs_output_wake(sb); + return; + } if (sb->sb_sel) { selwakeup(sb->sb_sel, sb->sb_flags & SB_COLL); sb->sb_sel = 0; *** ../netinet/tcp_usrreq.c.HOLD Sat Apr 19 02:13:50 1986 --- ../netinet/tcp_usrreq.c Sat Apr 19 03:25:37 1986 *************** *** 28,33 #include "../netinet/tcpip.h" #include "../netinet/tcp_debug.h" /* * TCP protocol interface to socket abstraction. */ --- 28,35 ----- #include "../netinet/tcpip.h" #include "../netinet/tcp_debug.h" + #include "../h/ioctl.h" + /* * TCP protocol interface to socket abstraction. */ *************** *** 220,225 /* SOME AS YET UNIMPLEMENTED HOOKS */ case PRU_CONTROL: error = EOPNOTSUPP; break; --- 222,234 ----- /* SOME AS YET UNIMPLEMENTED HOOKS */ case PRU_CONTROL: + if ((int)m == SIOCJNVS) { + if (tp->t_state == TCPS_ESTABLISHED) + error = nvs_ioc_join(so, *(int *)nam); + else + error = ENOTCONN; + break; + } error = EOPNOTSUPP; break; *** ioctl.h.HOLD Thu Feb 6 01:03:11 1986 --- ioctl.h Thu Feb 6 01:03:11 1986 *************** *** 394,399 #define SIOCATMARK _IOR(s, 7, int) /* at oob mark? */ #define SIOCSPGRP _IOW(s, 8, int) /* set process group */ #define SIOCGPGRP _IOR(s, 9, int) /* get process group */ #define SIOCADDRT _IOW(r, 10, struct rtentry) /* add route */ #define SIOCDELRT _IOW(r, 11, struct rtentry) /* delete route */ --- 394,400 ----- #define SIOCATMARK _IOR(s, 7, int) /* at oob mark? */ #define SIOCSPGRP _IOW(s, 8, int) /* set process group */ #define SIOCGPGRP _IOR(s, 9, int) /* get process group */ + #define SIOCJNVS _IOW(s, 71, int) /* join NVS pty&socket */ #define SIOCADDRT _IOW(r, 10, struct rtentry) /* add route */ #define SIOCDELRT _IOW(r, 11, struct rtentry) /* delete route */ *** socketvar.h.HOLD Fri Dec 6 15:08:43 1985 --- socketvar.h Fri Dec 6 15:08:43 1985 *************** *** 45,50 struct mbuf *sb_mb; /* the mbuf chain */ struct proc *sb_sel; /* process selecting read/write */ short sb_flags; /* flags, see below */ } so_rcv, so_snd; #define SB_LOCK 0x01 /* lock on data queue (so_rcv only) */ #define SB_WANT 0x02 /* someone is waiting to lock */ --- 45,55 ----- struct mbuf *sb_mb; /* the mbuf chain */ struct proc *sb_sel; /* process selecting read/write */ short sb_flags; /* flags, see below */ + /* + * the following is gross, but if the comment above is right, we can't + * add real pointer because it would make the structure too long + */ + short sb_nvs_index; /* index into nvs data */ } so_rcv, so_snd; #define SB_LOCK 0x01 /* lock on data queue (so_rcv only) */ #define SB_WANT 0x02 /* someone is waiting to lock */ *************** *** 53,58 #define SB_COLL 0x10 /* collision selecting */ #define SB_WAIT_SINGLE 0x20 /* someone waiting to lock (but only wakeup 1 process when available) */ short so_timeo; /* connection timeout */ u_short so_error; /* error affecting connection */ short so_oobmark; /* chars to oob mark */ --- 58,65 ----- #define SB_COLL 0x10 /* collision selecting */ #define SB_WAIT_SINGLE 0x20 /* someone waiting to lock (but only wakeup 1 process when available) */ + #define SB_NVS 0x40 /* socket crossbarred to NVS pty */ + #define SB_NVS_WAIT 0x80 /* waiting for output buffer space */ short so_timeo; /* connection timeout */ u_short so_error; /* error affecting connection */ short so_oobmark; /* chars to oob mark */ *** /src/usr.etc/in.telnetd.c.ORIG Thu Oct 31 21:26:44 1985 --- /src/usr.etc/in.telnetd.c Sun Apr 20 06:06:04 1986 *************** *** 52,58 */ char ptyibuf[BUFSIZ], *ptyip = ptyibuf; char ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf; ! char netibuf[BUFSIZ], *netip = netibuf; char netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf; int pcc, ncc; --- 54,60 ----- */ char ptyibuf[BUFSIZ], *ptyip = ptyibuf; char ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf; ! unsigned char netibuf[BUFSIZ], *netip = netibuf; char netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf; int pcc, ncc; *************** *** 62,67 extern int errno; char line[] = "/dev/ptyp0"; int childsid; main(argc, argv) char *argv[]; --- 64,70 ----- extern int errno; char line[] = "/dev/ptyp0"; int childsid; + char remotehost[256]; main(argc, argv) char *argv[]; *************** *** 140,145 host = hp->h_name; else host = ntoa(who->sin_addr); /* We identify who this is from. All of our children will * inherit this id from us. --- 143,149 ----- host = hp->h_name; else host = ntoa(who->sin_addr); + strcpy(remotehost,host); /* We identify who this is from. All of our children will * inherit this id from us. *************** *** 204,209 { int on = 1; char hostname[32]; net = f, pty = p; ioctl(f, FIONBIO, &on); --- 208,214 ----- { int on = 1; char hostname[32]; + int arg; net = f, pty = p; ioctl(f, FIONBIO, &on); *************** *** 220,227 * Show banner that getty never gave. */ gethostname(hostname, sizeof (hostname)); ! sprintf(nfrontp, BANNER, hostname, ""); ! nfrontp += strlen(nfrontp); for (;;) { int ibits = 0, obits = 0; register int c; --- 225,233 ----- * Show banner that getty never gave. */ gethostname(hostname, sizeof (hostname)); ! sprintf(netobuf, BANNER, hostname, ""); ! write(net,netobuf,strlen(netobuf)); ! for (;;) { register int c; *************** *** 223,229 sprintf(nfrontp, BANNER, hostname, ""); nfrontp += strlen(nfrontp); for (;;) { - int ibits = 0, obits = 0; register int c; /* --- 229,234 ----- write(net,netobuf,strlen(netobuf)); for (;;) { register int c; if (myopts[TELOPT_BINARY]) *************** *** 226,237 int ibits = 0, obits = 0; register int c; ! /* ! * Never look for input if there's still ! * stuff in the corresponding output buffer ! */ ! if (nfrontp - nbackp || pcc > 0) ! obits |= (1 << f); else ibits |= (1 << p); if (pfrontp - pbackp || ncc > 0) --- 231,238 ----- for (;;) { register int c; ! if (myopts[TELOPT_BINARY]) ! arg = 2 << 24; else arg = 1 << 24; arg |= 1 << 16; *************** *** 233,250 if (nfrontp - nbackp || pcc > 0) obits |= (1 << f); else ! ibits |= (1 << p); ! if (pfrontp - pbackp || ncc > 0) ! obits |= (1 << p); ! else ! ibits |= (1 << f); ! if (ncc < 0 && pcc < 0) ! break; ! select(16, &ibits, &obits, 0, 0); ! if (ibits == 0 && obits == 0) { ! sleep(5); ! continue; ! } /* * Something to read from the network... --- 234,242 ----- if (myopts[TELOPT_BINARY]) arg = 2 << 24; else ! arg = 1 << 24; ! arg |= 1 << 16; ! arg |= p; if (ioctl(f, SIOCJNVS, (char *)&arg) == -1) cleanup(); *************** *** 246,264 continue; } ! /* ! * Something to read from the network... ! */ ! if (ibits & (1 << f)) { ! ncc = read(f, netibuf, BUFSIZ); ! if (ncc < 0 && errno == EWOULDBLOCK) ! ncc = 0; ! else { ! if (ncc <= 0) ! break; ! netip = netibuf; ! } ! } /* * Something to read from the pty... --- 238,245 ----- arg |= 1 << 16; arg |= p; ! if (ioctl(f, SIOCJNVS, (char *)&arg) == -1) ! cleanup(); ncc = read(f, netibuf, BUFSIZ); if (ncc <= 0) *************** *** 260,293 } } ! /* ! * Something to read from the pty... ! */ ! if (ibits & (1 << p)) { ! pcc = read(p, ptyibuf, BUFSIZ); ! if (pcc < 0 && errno == EWOULDBLOCK) ! pcc = 0; ! else { ! if (pcc <= 0) ! break; ! ptyip = ptyibuf; ! } ! } ! ! while (pcc > 0) { ! if ((&netobuf[BUFSIZ] - nfrontp) < 2) ! break; ! c = *ptyip++ & 0377, pcc--; ! if (c == IAC) ! *nfrontp++ = c; ! *nfrontp++ = c; ! } ! if ((obits & (1 << f)) && (nfrontp - nbackp) > 0) ! netflush(); ! if (ncc > 0) ! telrcv(); ! if ((obits & (1 << p)) && (pfrontp - pbackp) > 0) ! ptyflush(); } cleanup(); } --- 241,252 ----- if (ioctl(f, SIOCJNVS, (char *)&arg) == -1) cleanup(); ! ncc = read(f, netibuf, BUFSIZ); ! if (ncc <= 0) ! {syslog(7,"in.telnetd ncc=%d, %d, %m",ncc,errno); ! break; ! } ! telrcv(); } syslog(7,"in.telnetd from %s terminated: %m",remotehost); cleanup(); *************** *** 289,294 if ((obits & (1 << p)) && (pfrontp - pbackp) > 0) ptyflush(); } cleanup(); } --- 248,254 ----- } telrcv(); } + syslog(7,"in.telnetd from %s terminated: %m",remotehost); cleanup(); } *************** *** 310,315 register int c; static int state = TS_DATA; struct sgttyb b; while (ncc > 0) { if ((&ptyobuf[BUFSIZ] - pfrontp) < 2) --- 270,276 ----- register int c; static int state = TS_DATA; struct sgttyb b; + struct auxchars aux; netip = netibuf; while (ncc > 0) { *************** *** 311,316 static int state = TS_DATA; struct sgttyb b; while (ncc > 0) { if ((&ptyobuf[BUFSIZ] - pfrontp) < 2) return; --- 272,278 ----- struct sgttyb b; struct auxchars aux; + netip = netibuf; while (ncc > 0) { c = *netip++ & 0377, ncc--; switch (state) { *************** *** 312,319 struct sgttyb b; while (ncc > 0) { - if ((&ptyobuf[BUFSIZ] - pfrontp) < 2) - return; c = *netip++ & 0377, ncc--; switch (state) { --- 274,279 ----- netip = netibuf; while (ncc > 0) { c = *netip++ & 0377, ncc--; switch (state) { *************** *** 322,332 state = TS_IAC; break; } ! if (inter > 0) ! break; ! *pfrontp++ = c; ! if (!myopts[TELOPT_BINARY] && c == '\r') ! state = TS_CR; break; case TS_CR: --- 282,289 ----- state = TS_IAC; break; } ! ptyobuf[0] = c; ! write(pty,ptyobuf,1); break; case TS_IAC: *************** *** 329,340 state = TS_CR; break; - case TS_CR: - if (c && c != '\n') - *pfrontp++ = c; - state = TS_DATA; - break; - case TS_IAC: switch (c) { --- 286,291 ----- write(pty,ptyobuf,1); break; case TS_IAC: switch (c) { *************** *** 352,358 * Are You There? */ case AYT: ! *nfrontp++ = BELL; break; /* --- 303,311 ----- * Are You There? */ case AYT: ! /* This assumes the Maryland ^T code is installed. ! * If not, you can following the original and ! * do ptyobuf[0] = BELL ! */ ! ioctl(pty, TIOCGAUXC, &aux); ! ptyobuf[0] = aux.t_usest; ! write(pty,ptyobuf,1); break; /* *************** *** 361,367 */ case EC: case EL: - ptyflush(); /* half-hearted */ ioctl(pty, TIOCGETP, &b); *pfrontp++ = (c == EC) ? b.sg_erase : b.sg_kill; --- 314,319 ----- */ case EC: case EL: ioctl(pty, TIOCGETP, &b); ptyobuf[0] = (c == EC) ? b.sg_erase : b.sg_kill; *************** *** 363,369 case EL: ptyflush(); /* half-hearted */ ioctl(pty, TIOCGETP, &b); ! *pfrontp++ = (c == EC) ? b.sg_erase : b.sg_kill; break; --- 315,321 ----- case EC: case EL: ioctl(pty, TIOCGETP, &b); ! ptyobuf[0] = (c == EC) ? b.sg_erase : b.sg_kill; write(pty,ptyobuf,1); break; *************** *** 365,370 ioctl(pty, TIOCGETP, &b); *pfrontp++ = (c == EC) ? b.sg_erase : b.sg_kill; break; /* --- 317,323 ----- ioctl(pty, TIOCGETP, &b); ptyobuf[0] = (c == EC) ? b.sg_erase : b.sg_kill; + write(pty,ptyobuf,1); break; /* *************** *** 388,394 continue; case IAC: ! *pfrontp++ = c; break; } state = TS_DATA; --- 341,348 ----- continue; case IAC: ! ptyobuf[0] = IAC; ! write(pty,ptyobuf,1); break; } state = TS_DATA; *************** *** 424,431 case TS_DONT: if (myopts[c]) { myopts[c] = 0; ! sprintf(nfrontp, wont, c); ! nfrontp += sizeof (wont) - 2; } state = TS_DATA; continue; --- 378,385 ----- case TS_DONT: if (myopts[c]) { myopts[c] = 0; ! sprintf(netobuf, wont, c); ! write(net,netobuf,sizeof (wont) - 2); } state = TS_DATA; continue; *************** *** 434,439 printf("telnetd: panic state=%d\n", state); exit(1); } } } --- 388,403 ----- printf("telnetd: panic state=%d\n", state); exit(1); } + if ((state != TS_DATA) && (ncc <= 0)) { + int ibits; + + ibits = 1 << net; + select(1,&ibits,0,0,0); + ncc = read(net, netibuf, BUFSIZ); + if (ncc <= 0) + {syslog(7,"in.telnetd ncc=%d, %d, %m",ncc,errno); + cleanup(); + } } } } *************** *** 435,440 exit(1); } } } willoption(option) --- 399,405 ----- cleanup(); } } + } } willoption(option) *************** *** 466,472 fmt = dont; break; } ! sprintf(nfrontp, fmt, option); nfrontp += sizeof (dont) - 2; } --- 431,437 ----- fmt = dont; break; } ! sprintf(netobuf, fmt, option); nfrontp += sizeof (dont) - 2; } *************** *** 527,533 break; } sprintf(nfrontp, fmt, option); ! nfrontp += sizeof (doopt) - 2; } mode(on, off) --- 492,498 ----- break; } sprintf(nfrontp, fmt, option); ! write(net, nfrontp, sizeof (doopt) - 2); } mode(on, off) *** /src/etc/in.rlogind.c.ORIG Sat Aug 3 20:53:22 1985 --- /src/etc/in.rlogind.c Sat Apr 19 07:10:27 1986 *************** *** 148,153 ioctl(p, TIOCPKT, &on); signal(SIGTSTP, SIG_IGN); signal(SIGCHLD, cleanup); for (;;) { int ibits = 0, obits = 0; --- 150,170 ----- ioctl(p, TIOCPKT, &on); signal(SIGTSTP, SIG_IGN); signal(SIGCHLD, cleanup); + /* BEGIN NVS support */ + /* + * If kernel supports high-performance NVS operation, + * invoke it and sleep until connection is broken. + * If remote end dies: ioctl() returns 0. + * If local job exits: ioctl() returns -1 / EINTR. + * Any other return from ioctl() is interpreted to mean + * the kernel doesn't support high-performance NVS + * operation, so do the job the standard Berkeley way. + */ + if (ioctl(f, SIOCJNVS, (char *)&p) == 0 || + errno == EINTR) + cleanup(); + /* END NVS support */ for (;;) { int ibits = 0, obits = 0; ****** new file: ../sys/tty_nvs.c ******** /* * Network virtual terminals via sockets - server end * * Rick Ace * New York Institute of Technology * Computer Graphics Laboratory * Old Westbury, New York 11568 */ /* * The code in this module supports the NVS, or remote end of the connection * (i.e., the link between the network and the pseudo-terminal). * The purpose of this code is to emulate efficiently the character- * shuffling functions performed by /etc/rlogind. The rlogin on the other * host will believe it is talking to rlogind, when it is in reality * talking to the NVS kernel software herein. * * The NVS kernel software achieves performance improvements by handling * incoming character traffic at interrupt level, eliminating the overhead * and delays resulting from scheduling a user-mode process (rlogind). * Outgoing character traffic is dumped directly into the socket by * Ptsnvsstart() in tty_pty.c, further eliminating the need for service * by a user-mode process. * * The implementation is broken into two main layers: * 1) high-level software, common to all NVSs (nvs_XXX subrs) * 2) protocol-specific stuff (tcp_XXX, etc.) * Presently, there is only a TCP implementation for layer # 2. */ #define NFS 1 #include "../h/param.h" #include "../h/conf.h" #include "../h/dir.h" #include "../h/user.h" #include "../h/proc.h" /* for lint */ #include "../h/file.h" #include "../h/tty.h" #include "../h/mbuf.h" #include "../h/protosw.h" #include "../h/socketvar.h" #ifdef NFS #include "../h/vnode.h" #include "../ufs/inode.h" #else NFS #include "../h/inode.h" #endif NFS #define nvs_emsg printf #define MAXNVS 32 /* should be in a parameter file */ /* * tp-to-socket correspondence table. * * This table is searched linearly. A highest-entry mark is maintained * to keep search overhead low. If there is a need to support large * numbers of NVSs, the linear search should be replaced by hashing. */ struct nvsj { struct tty *nvs_tp; /* tty/pty context, or 0 if free */ struct socket *nvs_so; /* socket context */ int nvs_in_mode; /* input side mode */ } nvsj[MAXNVS + 1]; /* in modes */ #define IN_RLOGIN 0 /* no processing */ #define IN_TELNET 1 /* telnet, normal */ #define IN_BINARY 2 /* telnet binary */ #define IN_CR 3 /* cr seen in nonbinary */ /* out modes */ #define OUT_RLOGIN 0 /* special urgent mode stuff */ #define OUT_TELNET 1 /* special IAC processing */ /* currently this is not implemented */ #define IAC 255 struct nvsj *nvshij = &nvsj[0]; /* highest active entry */ #ifndef lint int nvsj_n = MAXNVS; /* for kernel debug utilities */ #endif int nvsichz; extern struct socket *ptynvsso[ /* NPTY */ ]; /* socket indexed by pty minor */ extern int ptyoutmode[ /* NPTY */ ]; int ptsstart(), ptsnvsstart(), ptsnvsnet(); /* * Mate a tty and a socket * * Returns UNIX error code * Must be called at splimp */ nvs_add(tp, so, anvs, inmode, outmode) struct tty *tp; struct socket *so; struct nvsj **anvs; /* return addr of NVS context here */ int inmode, outmode; { register struct nvsj *nvs, *nx; extern int hz; /* XXX */ if (nvsichz == 0) nvsichz = hz / 3; /* XXX */ if (tp->t_oproc != ptsstart) return ENOTTY; /* not a pty or not in right mode */ nvs = 0; nx = &nvsj[MAXNVS - 1]; do { if (nx->nvs_tp) { if (nx->nvs_tp == tp || nx->nvs_so == so) { return EBUSY; /* tty or socket already an NVS */ } } else nvs = nx; /* remember lowest empty slot */ } while (--nx >= &nvsj[0]); if (nvs == 0) { return ENFILE; /* all slots are in use */ } /* * All is clear, mate them */ if (nvs > nvshij) nvshij = nvs; /* update highest active entry */ *anvs = nvs; /* pass context pointer to caller */ nvs->nvs_so = so; nvs->nvs_in_mode = inmode; nvs->nvs_tp = tp; so->so_rcv.sb_flags |= SB_NVS; so->so_rcv.sb_nvs_index = nvs - nvsj; so->so_snd.sb_nvs_index = nvs - nvsj; tp->t_oproc = ptsnvsstart; ptyoutmode[minor(tp->t_dev)] = outmode; ptynvsso[minor(tp->t_dev)] = so; sbwakeup(&so->so_rcv); /* get characters flowing */ ptsnvsstart(tp); /* ditto */ return 0; } /* * Undo a tty/socket correspondence * * Must be called at splimp */ nvs_del(nvs) struct nvsj *nvs; { register struct tty *tp; nvs->nvs_so->so_rcv.sb_flags &= ~SB_NVS; tp = nvs->nvs_tp; if (tp->t_oproc == ptsnvsstart) tp->t_oproc = ptsstart; ptynvsso[minor(tp->t_dev)] = 0; /* * Delete this entry and adjust Nvshij if necessary */ nvs->nvs_tp = 0; /* mark entry as free */ if (nvshij == nvs) while (nvshij > &nvsj[0] && nvshij->nvs_tp == 0) nvshij--; } /* * Called from sbwakeup() when NVS has incoming characters from the net, * to transfer those characters from the socket to the tty's input queue * * Logic here is similar to that of soreceive() in uipc_socket.c */ nvs_input(sb) struct sockbuf *sb; /* so_rcv */ { #define STRUCT_OFF(strnam, elem) \ ((char *)&((struct strnam *)0)->elem - (char *)0) register struct socket *so; register struct nvsj *nvs; register struct tty *tp; register struct mbuf *m; register unsigned char *cp; register int n; struct mbuf *mz; int nvsmode,xflags; unsigned char inchar; /* * Convert so_rcv address to socket address (a bit gross), * then search the active-connection table for that socket */ so = (struct socket *)((char *)sb - STRUCT_OFF(socket, so_rcv)); nvs = &nvsj[sb->sb_nvs_index]; if ((tp = nvs->nvs_tp) == 0) { nvs_emsg("nvs input no tp %x\n", so); return; } nvsmode = nvs->nvs_in_mode; /* flags. use a register for speed */ xflags = tp->t_xflags; /* Pyramid extra flags for ATT mode */ /* * Have located the tty, now pass it the data */ so->so_state &= ~SS_RCVATMARK; /* ignore out-of-band data */ so->so_oobmark = 0; /* ditto */ /* * Process each mbuf on the socket's rcv queue */ while (so->so_rcv.sb_cc > 0) { m = so->so_rcv.sb_mb; if (m == 0) panic("nvs_input"); cp = mtod(m, unsigned char *); for (n = m->m_len; --n >= 0; ) { inchar = *cp++; switch (nvsmode) { case IN_CR: nvsmode = IN_TELNET; if ((inchar == 0) || (inchar == '\n')) break; case IN_TELNET: if (inchar == '\r') nvsmode = IN_CR; case IN_BINARY: if (inchar == IAC) { n = m->m_len - (n + 1); /* chars read */ m->m_off += n; so->so_rcv.sb_cc -= n; m->m_len -= n; so->so_rcv.sb_flags &= ~SB_NVS; wakeup((caddr_t)nvs); return; } case IN_RLOGIN: /* * The code from here to the call to linesw is Pyramid-specific, because * of implementing ATT terminal handling */ if (xflags&BTLON) { inchar = inchar&0377; if (xflags&XISTRIP) inchar &= 0177; if ((inchar == '\n') && (xflags&XINLCR)) { inchar = '\r'; } else if (inchar == '\r') { if (xflags&XIGNCR) { break; } else if (xflags&XICRNL) inchar = '\n'; } if ((inchar >= 'A') && (inchar <= 'Z') && (xflags&XIUCLC)) inchar |= 040; } (*linesw[tp->t_line].l_rint)(inchar, tp); } } sbfree(&so->so_rcv, m); MFREE(m, mz); so->so_rcv.sb_mb = m = mz; } nvs->nvs_in_mode = nvsmode; /* put back possibly changed flags */ /* * Notify protocol that more space is available in the socket */ if ((so->so_state & SS_CANTRCVMORE) == 0 && so->so_proto->pr_flags & PR_WANTRCVD && so->so_pcb) (*so->so_proto->pr_usrreq)(so, PRU_RCVD, (struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0); /* * If socket closes at remote end first (not the most common case), * wake up the agent process so it can exit. */ if (so->so_state & (SS_CANTRCVMORE | SS_CANTSENDMORE)) { so->so_rcv.sb_flags &= ~SB_NVS; wakeup((caddr_t)nvs); } } /* * Handle ioctl request on socket to join socket and pty, called * from protocol ioctl logic. Protocol-specific validation is * done before you get here. * * If the connection was successfully established, sleep and wait * until it is broken at the remote end, or until a signal is * received on the local end. * * Returns * 0 = remote NVT closed the connection * EINTR = interrupted by signal * Anything else = an error establishing the connection */ nvs_ioc_join(so, ptyfd) struct socket *so; int ptyfd; /* pty file descriptor */ { register struct file *fp; register struct inode *ip; register struct tty *tp; int error, s; struct nvsj *nvsp; int ptcopen(); int inmode, outmode; outmode = (ptyfd >> 16) & 0xFF; inmode = ptyfd >> 24; ptyfd = ptyfd & 0xFFFF; /* * Validate file descriptor and ensure it references a pty */ if ((unsigned)ptyfd >= NOFILE || (fp = u.u_ofile[ptyfd]) == 0) return EBADF; #ifdef NFS { register struct vnode *vp; vp = (struct vnode *)fp->f_data; if (fp->f_type != DTYPE_VNODE || vp->v_op != &ufs_vnodeops) return ENOTTY; ip = VTOI(vp); if ((ip->i_mode & IFMT) != IFCHR || cdevsw[major(ip->i_rdev)].d_open != ptcopen) return ENOTTY; /* really ENOTPTY :-) */ } #else NFS ip = (struct inode *)fp->f_data; if (fp->f_type != DTYPE_INODE || (ip->i_mode & IFMT) != IFCHR || cdevsw[major(ip->i_rdev)].d_open != ptcopen) return ENOTTY; /* really ENOTPTY :-) */ #endif NFS /* * Argument socket and pty are valid, now join them and wait */ tp = cdevsw[major(ip->i_rdev)].d_ttys; s = splimp(); if (error = nvs_add(tp + minor(ip->i_rdev), so, &nvsp, inmode, outmode)) { splx(s); return error; } if (setjmp(&u.u_qsave)) { /* * The process received a signal */ nvs_del(nvsp); /* disassociate pty&socket */ splx(s); return EINTR; } while (nvsp->nvs_so->so_rcv.sb_flags & SB_NVS) sleep((caddr_t)nvsp, PZERO + 1); /* wait for signal or disconnect */ /* * The remote disconnected. */ nvs_del(nvsp); /* disassociate pty&socket */ splx(s); return 0; } /* * nvs_output_wake is called from sbwakeup when more space becomes * available in the socket output buffer. The code that clears * SB_NVS_WAIT assumes that this is called at iplnet. Let's hope * it is. */ nvs_output_wake(sb) struct sockbuf *sb; /* so_snd */ { register struct socket *so; register struct nvsj *nvs; register struct tty *tp; nvs = &nvsj[sb->sb_nvs_index]; if ((tp = nvs->nvs_tp) == 0) { nvs_emsg("nvs output wake no tp %x\n", sb); return; } sb->sb_flags &= ~(SB_NVS_WAIT | SB_WAIT); ptsnvsnet(tp); /* calls nvs_output immediately */ } /* * Move characters from pty outq to network * TS_BUSY is assumed to be set upon entry * * Must be called at or below net IPL */ /*ARGSUSED*/ nvs_output(tp, urgentp) struct tty *tp; int *urgentp; /* call by reference because I can zero it */ { register struct mbuf *m; register struct socket *so; register int error, s, space; unsigned char * dp; int outmode; int iacs,chars; s = splnet(); if ((so = ptynvsso[minor(tp->t_dev)]) == 0) { /* paranoid */ /* * no longer an NVS! */ statemask(tp,~TS_BUSY); if (tp->t_oproc) (*tp->t_oproc)(tp); goto rspl; } if (so->so_state & SS_CANTSENDMORE) { while (getc(&tp->t_outq) >= 0) ; *urgentp = 0; } outmode = ptyoutmode[minor(tp->t_dev)]; /* * If there is urgent data to send, ship it now */ if (*urgentp && (outmode == OUT_RLOGIN)) { MGET(m, M_DONTWAIT, MT_DATA); if (m == 0) goto out; *mtod(m, unsigned char *) = *urgentp; *urgentp = 0; m->m_len = 1; error = (*so->so_proto->pr_usrreq)(so, PRU_SENDOOB, m, (struct mbuf *)0, (struct mbuf *)0); if (error) nvs_emsg("nvs SENDOOB %d\n", error); goto out; } else *urgentp = 0; if ((tp->t_state & TS_TTSTOP) && (outmode != OUT_RLOGIN)) { spl5(); statemask(tp,~TS_BUSY); goto out2; } while (tp->t_outq.c_cc > 0) { /* * Send a chunk of character traffic that may be pending */ /* * We check for space in the socket first, because the most * common wait is due to flow control from the other end. * Since we are using a form of busy wait, we want to detect * this as soon as possible */ space = sbspace(&so->so_snd); if (space < so->so_snd.sb_lowat) { /* not enough space */ so->so_snd.sb_flags |= SB_NVS_WAIT | SB_WAIT; /* wait till space */ spl5(); /* needed at out2 */ goto out2; /* don't resched. net code will call us when space */ } /* * Each time through the loop we do one contiguous piece of * data from the character list. We have to do it this way * in order to be able to check for iacs efficiently. */ iacs = 0; chars = ndqb(&tp->t_outq,0); if (outmode != OUT_RLOGIN) { /* look for IAC's */ int count; unsigned char * cp = (unsigned char *)tp->t_outq.c_cf; for (count = chars; count > 0; count--) if (*cp++ == IAC) iacs++; } /* * at this point chars is number of chars in first piece of buffer, * and iacs is number of iacs in that */ /* * The following code is specific to Pyramid release 3.0 * We are trying to copy data from the pty buffer out to the * network. On the Pyramid, sbnext is used to find where to * copy the data to. It returns (in dp) a pointer into an * mbuf. If there was already data on the queue, this is * a pointer into an existing mbuf. If not, it allocates a * new one. It returns the amount of space left in the mbuf. * It sets m_len in the mbuf assuming that you are actually * going to put that much data in. * * On a VAX, things work differently. Instead of calling * sbnext to find a place to put your data, you allocate * an mbuf yourself. Then you copy the data into it as * below. Further down, you will find a call to pr_usrreq. * On a VAX, the argument I have given as "1" would be the * mbuf. The second line below, with sbnext in it, and the * following two lines, "if (space <= 0) goto out;", should * probably be replaced by * MGET(m, M_DONTWAIT, MT_DATA); * if (m == 0) * goto out; * if (space > MLEN) * space = MLEN; * dp = mtod(m, unsigned char *); * m->m_len = space; * This effectively simulates sbnext, although in a somewhat * less intelligent way than Pyramid's actual code. * In the call to pr_usrreq below, you should probably change * the argument "1" to "m". */ space = MIN(space, chars + iacs); space = sbnext(&so->so_snd,&dp,space); if (space <= 0) goto out; /* no mbufs. this does a busy wait until space */ /* * This is an optimization. If there are no IAC's, we can just copy * the data from the pty directly into the tcp mbuf. If there are * IAC's, we have to turn each single IAC into two IAC's. We do * this by copying one character at a time. If we decide to turn bare * CR into CR NUL, as required by the spec, we will have to do something * similar when we find a bare CR. We will also have to remember when * the last character we process is a CR, for handling next time around. */ if (iacs == 0) { q_to_b(&tp->t_outq, dp, space); so->so_snd.sb_cc += space; } else { int count = space; while (count-- > 1) { /* leave one extra for iac */ unsigned char outchar; outchar = getc(&tp->t_outq); *dp++ = outchar; if (outchar == IAC) { *dp++ = outchar; count--; } } so->so_snd.sb_mb->m_len -= count; /* either 1 or 0 left over */ so->so_snd.sb_cc += space - count; } error = (*so->so_proto->pr_usrreq)(so, PRU_SEND, 1, (struct mbuf *)0, (struct mbuf *)0); #ifdef lint /* Same actuals as above, better information for lint */ error = tcp_usrreq(so, PRU_SEND, 1, (struct mbuf *)0, (struct mbuf *)0); #endif } out: /* * Perpetuate myself if work remains */ (void) spl5(); /* block out tty activity */ if (tp->t_outq.c_cc > 0 || *urgentp) timeout(ptsnvsnet, (caddr_t)tp, nvsichz); /* TS_BUSY remains set */ else statemask(tp,~TS_BUSY); out2: /* * Wake up sleepers */ if (tp->t_outq.c_cc <= TTLOWAT(tp)) { if (tp->t_state&TS_ASLEEP) { statemask(tp,~TS_ASLEEP); wakeup((caddr_t)&tp->t_outq); } if (tp->t_wsel) { selwakeup(tp->t_wsel, tp->t_state & TS_WCOLL); tp->t_wsel = 0; statemask(tp,~TS_WCOLL); } } rspl: splx(s); }