freebsd-dev/usr.sbin/ppp/modem.c
Doug Rabson 76bd0c0a9d Some patches to ppp which improve stability. I have been running a
ppp based on these patches for about 3 weeks with no downtime.

The original submitters comments:

Two features iijppp has over kernel ppp that I like are predictor1
compression and demand dialing.  Here are a few bug fixes.

I expanded the priority queueing scheme and discovered it was broken
due to the assignment at ip.c line 300.  All packets were being
queued at the same priority.

Fixing priority queueing broke predictor1 compression.  Packets
were compressed before being queued and predictor1 worked as long
as the packets were popped off the queue in the same order they
were pushed onto the queue.

There were a few byte order problems in IP header tests also.

There is a recursion problem in SendLqrReport().  LcpClose() is
called when "Too many echo packets are lost" which winds up in
SendLqrReport() again.  I believe the original intention was to
just stop the LQR timer with the call to StopLqr() but the side
effects hurt.

Submitted by:	John Capo <jc@irbs.com>
1996-01-30 11:08:50 +00:00

788 lines
16 KiB
C

/*
* PPP Modem handling module
*
* Written by Toshiharu OHNO (tony-o@iij.ad.jp)
*
* Copyright (C) 1993, Internet Initiative Japan, Inc. All rights reserverd.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the Internet Initiative Japan, Inc. The name of the
* IIJ may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* $Id: modem.c,v 1.11 1996/01/11 17:48:54 phk Exp $
*
* TODO:
*/
#include "fsm.h"
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <errno.h>
#include <time.h>
#include "hdlc.h"
#include "lcp.h"
#include "ip.h"
#include "modem.h"
#include "vars.h"
#ifndef O_NONBLOCK
#ifdef O_NDELAY
#define O_NONBLOCK O_NDELAY
#endif
#endif
extern int DoChat();
static int mbits; /* Current DCD status */
static int connect_time; /* connection time */
static int connect_count;
static struct pppTimer ModemTimer;
static char uucplock[10];
extern int uu_lock(), uu_unlock();
extern void PacketMode();
#define Online (mbits & TIOCM_CD)
static struct mbuf *modemout;
static struct mqueue OutputQueues[PRI_LINK+1];
static int dev_is_modem;
#undef QDEBUG
void
Enqueue(queue, bp)
struct mqueue *queue;
struct mbuf *bp;
{
if (queue->last) {
queue->last->pnext = bp;
queue->last = bp;
} else
queue->last = queue->top = bp;
queue->qlen++;
#ifdef QDEBUG
logprintf("Enqueue: len = %d\n", queue->qlen);
#endif
}
struct mbuf *
Dequeue(queue)
struct mqueue *queue;
{
struct mbuf *bp;
#ifdef QDEBUG
logprintf("Dequeue: len = %d\n", queue->qlen);
#endif
bp = queue->top;
if (bp) {
queue->top = queue->top->pnext;
queue->qlen--;
if (queue->top == NULL) {
queue->last = queue->top;
#ifdef QDEBUG
if (queue->qlen)
logprintf("!!! not zero (%d)!!!\n", queue->qlen);
#endif
}
}
return(bp);
}
static struct speeds{
int nspeed;
speed_t speed;
} speeds[] = {
#ifdef B50
{ 50, B50, },
#endif
#ifdef B75
{ 75, B75, },
#endif
#ifdef B110
{ 110, B110, },
#endif
#ifdef B134
{ 134, B134, },
#endif
#ifdef B150
{ 150, B150, },
#endif
#ifdef B200
{ 200, B200, },
#endif
#ifdef B300
{ 300, B300, },
#endif
#ifdef B600
{ 600, B600, },
#endif
#ifdef B1200
{ 1200, B1200, },
#endif
#ifdef B1800
{ 1800, B1800, },
#endif
#ifdef B2400
{ 2400, B2400, },
#endif
#ifdef B4800
{ 4800, B4800, },
#endif
#ifdef B9600
{ 9600, B9600, },
#endif
#ifdef B19200
{ 19200, B19200, },
#endif
#ifdef B38400
{ 38400, B38400, },
#endif
#ifndef _POSIX_SOURCE
#ifdef B7200
{ 7200, B7200, },
#endif
#ifdef B14400
{ 14400, B14400, },
#endif
#ifdef B28800
{ 28800, B28800, },
#endif
#ifdef B57600
{ 57600, B57600, },
#endif
#ifdef B76800
{ 76800, B76800, },
#endif
#ifdef B115200
{ 115200, B115200, },
#endif
#ifdef B230400
{ 230400, B230400, },
#endif
#ifdef EXTA
{ 19200, EXTA, },
#endif
#ifdef EXTB
{ 38400, EXTB, },
#endif
#endif /*_POSIX_SOURCE */
{ 0, 0 }
};
int SpeedToInt(speed)
speed_t speed;
{
struct speeds *sp;
for (sp = speeds; sp->nspeed; sp++) {
if (sp->speed == speed) {
return(sp->nspeed);
}
}
return 0;
}
speed_t IntToSpeed(nspeed)
int nspeed;
{
struct speeds *sp;
for (sp = speeds; sp->nspeed; sp++) {
if (sp->nspeed == nspeed) {
return(sp->speed);
}
}
return B0;
}
static time_t uptime;
void
DownConnection()
{
LogPrintf(LOG_PHASE, "Disconnected!\n");
LogPrintf(LOG_PHASE, "Connect time: %d secs\n", time(NULL) - uptime);
CloseModem();
LcpDown();
connect_time = 0;
}
/*
* ModemTimeout() watches DCD signal and notifies if it's status is changed.
*
*/
void
ModemTimeout()
{
int ombits = mbits;
int change;
StopTimer(&ModemTimer);
if (Online)
connect_time++;
StartTimer(&ModemTimer);
if (dev_is_modem) {
ioctl(modem, TIOCMGET, &mbits);
change = ombits ^ mbits;
if (change & TIOCM_CD) {
if (Online) {
time(&uptime);
LogPrintf(LOG_PHASE, "*Connected!\n");
connect_count++;
/*
* In dedicated mode, start packet mode immediate
* after we detected carrier.
*/
if (mode & MODE_DEDICATED)
PacketMode();
} else {
DownConnection();
}
}
} else {
if (!Online) {
time(&uptime);
LogPrintf(LOG_PHASE, "Connected!\n");
mbits = TIOCM_CD;
connect_count++;
connect_time = 0;
} else if (uptime == 0) {
time(&uptime);
}
}
}
void
StartModemTimer()
{
connect_time = 0;
StopTimer(&ModemTimer);
ModemTimer.state = TIMER_STOPPED;
ModemTimer.load = SECTICKS;
ModemTimer.func = ModemTimeout;
StartTimer(&ModemTimer);
}
struct parity {
char *name;
char *name1;
int set;
} validparity[] = {
{ "even", "P_EVEN", CS7 | PARENB }, { "odd", "P_ODD", CS7 | PARENB | PARODD },
{ "none", "P_ZERO", CS8 }, { NULL, 0 },
};
int
GetParityValue(str)
char *str;
{
struct parity *pp;
for (pp = validparity; pp->name; pp++) {
if (strcasecmp(pp->name, str) == 0 ||
strcasecmp(pp->name1, str) == 0) {
VarParity = pp->set;
return(pp->set);
}
}
return(-1);
}
int
ChangeParity(str)
char *str;
{
struct termios rstio;
int val;
val = GetParityValue(str);
if (val > 0) {
VarParity = val;
tcgetattr(modem, &rstio);
rstio.c_cflag &= ~(CSIZE|PARODD|PARENB);
rstio.c_cflag |= val;
tcsetattr(modem, TCSADRAIN, &rstio);
}
return(val);
}
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
int
OpenConnection(host, port)
char *host, *port;
{
struct sockaddr_in dest;
int sock;
struct hostent *hp;
struct servent *sp;
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr(host);
if (dest.sin_addr.s_addr == INADDR_NONE) {
hp = gethostbyname(host);
if (hp) {
bcopy(hp->h_addr_list[0], &dest.sin_addr.s_addr, 4);
} else {
printf("unknown host: %s\n", host);
return(-1);
}
}
dest.sin_port = htons(atoi(port));
if (dest.sin_port == 0) {
sp = getservbyname(port, "tcp");
if (sp) {
dest.sin_port = sp->s_port;
} else {
printf("unknown service: %s\n", port);
return(-1);
}
}
LogPrintf(LOG_PHASE, "Connected to %s:%s\n", host, port);
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock < 0) {
return(sock);
}
if (connect(sock, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
printf("connection failed.\n");
return(-1);
}
return(sock);
}
int
OpenModem(mode)
int mode;
{
struct termios rstio;
int oldflag;
char *host, *cp, *port;
mbits = 0;
if (mode & MODE_DIRECT) {
if (isatty(0))
modem = open(ctermid(NULL), O_RDWR|O_NONBLOCK);
} else if (modem == 0) {
if (strncmp(VarDevice, "/dev", 4) == 0) {
strcpy(uucplock, rindex(VarDevice, '/')+1);
if (uu_lock(uucplock) < 0) {
LogPrintf(LOG_PHASE, "Modem %s is in use\n", VarDevice);
return(-1);
}
modem = open(VarDevice, O_RDWR|O_NONBLOCK);
if (modem < 0) {
LogPrintf(LOG_PHASE, "Open Failed %s\n", VarDevice);
(void) uu_unlock(uucplock);
return(modem);
}
} else {
/* XXX: PPP over TCP */
cp = index(VarDevice, ':');
if (cp) {
*cp = 0;
host = VarDevice;
port = cp + 1;
if (*host && *port) {
modem = OpenConnection(host, port);
*cp = ':'; /* Don't destroy VarDevice */
if (modem < 0) return(-1);
} else {
*cp = ':'; /* Don't destroy VarDevice */
return(-1);
}
} else
return(-1);
}
}
/* This code gets around the problem of closing descriptor 0
* when it should not have been closed and closing descriptor 1
* when the telnet connection dies. Since this program always
* opens a descriptor for the modem in auto and direct mode,
* having to dup the descriptor here is a fatal error.
*
* With the other changes I have made this should no longer happen.
* JC
*/
while (modem < 3)
{
logprintf("Duping modem fd %d\n", modem);
modem = dup(modem);
}
/*
* If we are working on tty device, change it's mode into
* the one desired for further operation. In this implementation,
* we assume that modem is configuted to use CTS/RTS flow control.
*/
dev_is_modem = isatty(modem) || DEV_IS_SYNC;
if (DEV_IS_SYNC)
sleep(1);
if (dev_is_modem && !DEV_IS_SYNC) {
tcgetattr(modem, &rstio);
#ifdef DEBUG
logprintf("## modem = %d\n", modem);
logprintf("modem (get): iflag = %x, oflag = %x, cflag = %x\n",
rstio.c_iflag, rstio.c_oflag, rstio.c_cflag);
#endif
#define USE_CTSRTS
#ifdef USE_CTSRTS
rstio.c_cflag = (CS8 | CREAD | CLOCAL | CCTS_OFLOW|CRTS_IFLOW);
#else
rstio.c_cflag = (CS8 | CREAD | CLOCAL);
#endif
if ((mode & MODE_DIRECT) == 0) {
/*
* If we are working as direct mode, don't change tty speed.
*/
rstio.c_cflag &= ~(CSIZE|PARENB|PARODD);
rstio.c_cflag |= VarParity;
cfsetspeed(&rstio, IntToSpeed(VarSpeed));
}
rstio.c_iflag |= (IGNBRK | ISTRIP | IGNPAR | IXON | IXOFF);
rstio.c_iflag &= ~(BRKINT|ICRNL|IXANY|IMAXBEL);
rstio.c_lflag = 0;
rstio.c_oflag &= ~OPOST;
#ifdef notdef
rstio.c_cc[VMIN] = 10;
rstio.c_cc[VTIME] = 1;
#else
rstio.c_cc[VMIN] = 1;
rstio.c_cc[VTIME] = 0;
#endif
tcsetattr(modem, TCSADRAIN, &rstio);
#ifdef DEBUG
logprintf("modem (put): iflag = %x, oflag = %x, cflag = %x\n",
rstio.c_iflag, rstio.c_oflag, rstio.c_cflag);
#endif
if (!(mode & MODE_DIRECT))
ioctl(modem, TIOCMGET, &mbits);
#ifdef DEBUG
fprintf(stderr, "modem control = %o\n", mbits);
#endif
oldflag = fcntl(modem, F_GETFL, 0);
fcntl(modem, F_SETFL, oldflag & ~O_NONBLOCK);
}
StartModemTimer();
return(modem);
}
int
ModemSpeed()
{
struct termios rstio;
tcgetattr(modem, &rstio);
return(SpeedToInt(cfgetispeed(&rstio)));
}
static struct termios modemios;
/*
* Put modem tty line into raw mode which is necessary in packet mode operation
*/
int
RawModem(modem)
int modem;
{
struct termios rstio;
int oldflag;
if (!isatty(modem) || DEV_IS_SYNC)
return(0);
if (!(mode & MODE_DIRECT) && modem && !Online) {
#ifdef DEBUG
logprintf("mode = %d, modem = %d, mbits = %x\n", mode, modem, mbits);
#endif
#ifdef notdef
return(-1);
#endif
}
tcgetattr(modem, &rstio);
modemios = rstio;
rstio.c_cflag &= ~(CSIZE|PARENB|PARODD);
rstio.c_cflag |= CS8;
rstio.c_iflag &= ~(ISTRIP|IXON|IXOFF|BRKINT|ICRNL|INLCR);
tcsetattr(modem, TCSADRAIN, &rstio);
oldflag = fcntl(modem, F_GETFL, 0);
fcntl(modem, F_SETFL, oldflag | O_NONBLOCK);
#ifdef DEBUG
oldflag = fcntl(modem, F_GETFL, 0);
logprintf("modem (put2): iflag = %x, oflag = %x, cflag = %x\n",
rstio.c_iflag, rstio.c_oflag, rstio.c_cflag);
logprintf("flag = %x\n", oldflag);
#endif
return(0);
}
void
UnrawModem(modem)
int modem;
{
int oldflag;
if (isatty(modem)) {
tcsetattr(modem, TCSADRAIN, &modemios);
oldflag = fcntl(modem, F_GETFL, 0);
fcntl(modem, F_SETFL, oldflag & ~O_NONBLOCK);
}
}
void
HangupModem(flag)
int flag;
{
struct termios tio;
if (!isatty(modem)) {
mbits &= ~TIOCM_DTR;
close(modem);
modem = 0; /* Mark as modem has closed */
return;
}
if (modem && Online) {
mbits &= ~TIOCM_DTR;
#ifdef __bsdi__ /* not a POSIX way */
ioctl(modem, TIOCMSET, &mbits);
#else
tcgetattr(modem, &tio);
cfsetspeed(&tio, B0);
tcsetattr(modem, TCSANOW, &tio);
#endif
sleep(1);
#ifdef notdef
mbits &= ~TIOCM_CD;
#endif
}
/*
* If we are working as dedicated mode, never close it
* until we are directed to quit program.
*/
if (modem && (flag || !(mode & MODE_DEDICATED))) {
ModemTimeout(); /* XXX */
StopTimer(&ModemTimer); /* XXX */
/* ModemTimeout() may call DownConection() to close the modem
* resulting in modem == 0.
*/
if (modem)
{
tcflush(modem, TIOCFLUSH);
UnrawModem(modem);
close(modem);
}
(void) uu_unlock(uucplock);
modem = 0; /* Mark as modem has closed */
} else if (modem) {
mbits |= TIOCM_DTR;
#ifndef notyet
ioctl(modem, TIOCMSET, &mbits);
#else
tcgetattr(modem, &ts);
cfsetspeed(&ts, IntToSpeed(VarSpeed));
tcsetattr(modem, TCSADRAIN, &ts);
#endif
}
}
void
CloseModem()
{
if (modem >= 3)
{
close(modem);
modem = 0;
}
(void) uu_unlock(uucplock);
}
/*
* Write to modem. Actualy, requested packets are queued, and goes out
* to the line when ModemStartOutput() is called.
*/
void
WriteModem(pri, ptr, count)
int pri;
char *ptr;
int count;
{
struct mbuf *bp;
bp = mballoc(count, MB_MODEM);
bcopy(ptr, MBUF_CTOP(bp), count);
/* Should be NORMAL and LINK only.
* All IP frames get here marked NORMAL.
*/
Enqueue(&OutputQueues[pri], bp);
}
void
ModemOutput(pri, bp)
int pri;
struct mbuf *bp;
{
struct mbuf *wp;
int len;
len = plength(bp);
wp = mballoc(len, MB_MODEM);
mbread(bp, MBUF_CTOP(wp), len);
Enqueue(&OutputQueues[pri], wp);
ModemStartOutput(modem);
}
int
ModemQlen()
{
struct mqueue *queue;
int len = 0;
int i;
for ( i = PRI_NORMAL; i <= PRI_LINK; i ++ ) {
queue = &OutputQueues[i];
len += queue->qlen;
}
return(len);
}
void
ModemStartOutput(fd)
int fd;
{
struct mqueue *queue;
int nb, nw, i;
if (modemout == NULL && ModemQlen() == 0)
IpStartOutput();
if (modemout == NULL) {
i = PRI_LINK;
for (queue = &OutputQueues[PRI_LINK]; queue >= OutputQueues; queue--) {
if (queue->top) {
modemout = Dequeue(queue);
#ifdef QDEBUG
if (i > PRI_NORMAL) {
struct mqueue *q;
q = &OutputQueues[0];
logprintf("output from queue %d, normal has %d\n", i, q->qlen);
}
logprintf("Dequeue(%d): ", i);
#endif
break;
}
i--;
}
}
if (modemout) {
nb = modemout->cnt;
if (nb > 1600) nb = 1600;
if (fd == 0) fd = 1; /* XXX WTFO! This is bogus */
nw = write(fd, MBUF_CTOP(modemout), nb);
#ifdef QDEBUG
logprintf("wrote: %d(%d)\n", nw, nb);
LogDumpBuff(LOG_HDLC, "modem write", MBUF_CTOP(modemout), nb);
#endif
if (nw > 0) {
modemout->cnt -= nw;
modemout->offset += nw;
if (modemout->cnt == 0) {
modemout = mbfree(modemout);
#ifdef QDEBUG
logprintf(" mbfree\n");
#endif
}
} else if (nw < 0) {
if (errno != EAGAIN)
perror("modem write");
}
}
}
int
DialModem()
{
char ScriptBuffer[200];
strcpy(ScriptBuffer, VarDialScript);
if (DoChat(ScriptBuffer) > 0) {
fprintf(stderr, "dial OK!\n");
strcpy(ScriptBuffer, VarLoginScript);
if (DoChat(ScriptBuffer) > 0) {
fprintf(stderr, "login OK!\n");
return(1);
} else {
fprintf(stderr, "login failed.\n");
}
ModemTimeout(); /* Dummy call to check modem status */
}
else
fprintf(stderr, "dial failed.\n");
HangupModem(0);
return(0);
}
int
ShowModemStatus()
{
#ifdef TIOCOUTQ
int nb;
#endif
printf("device: %s speed: ", VarDevice);
if (DEV_IS_SYNC)
printf("sync\n");
else
printf("%d\n", VarSpeed);
switch (VarParity & CSIZE) {
case CS7:
printf("cs7, ");
break;
case CS8:
printf("cs8, ");
break;
}
if (VarParity & PARENB) {
if (VarParity & PARODD)
printf("odd parity\n");
else
printf("even parity\n");
} else
printf("none parity\n");
#ifdef DEBUG
printf("fd = %d, modem control = %o\n", modem, mbits);
#endif
printf("connect count: %d\n", connect_count);
#ifdef TIOCOUTQ
ioctl(modem, TIOCOUTQ, &nb);
printf("outq: %d\n", nb);
#endif
printf("outqlen: %d\n", ModemQlen());
printf("DialScript = %s\n", VarDialScript);
printf("LoginScript = %s\n", VarLoginScript);
printf("PhoneNumber = %s\n", VarPhone);
return(1);
}