/*
* ccp.c - PPP Compression Control Protocol.
*
* Copyright (c) 2000 by Sun Microsystems, Inc.
* All rights reserved.
*
* Copyright (c) 1994 The Australian National University.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation is hereby granted, provided that the above copyright
* notice appears in all copies. This software is provided without any
* warranty, express or implied. The Australian National University
* makes no representations about the suitability of this software for
* any purpose.
*
* IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY
* PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
* THE AUSTRALIAN NATIONAL UNIVERSITY HAVE BEEN ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO
* OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
* OR MODIFICATIONS.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdlib.h>
#include <string.h>
#include "pppd.h"
#include "fsm.h"
#include "ccp.h"
#include <net/ppp-comp.h>
#endif
/*
* Command-line options.
*/
"Disable CCP negotiation" },
"Disable CCP negotiation" },
"Request BSD-Compress packet compression" },
"don't allow BSD-Compress", OPT_A2COPY,
&ccp_allowoptions[0].bsd_compress },
"don't allow BSD-Compress", OPT_A2COPY,
&ccp_allowoptions[0].bsd_compress },
"request Deflate compression" },
"don't allow Deflate compression", OPT_A2COPY,
&ccp_allowoptions[0].deflate },
"don't allow Deflate compression", OPT_A2COPY,
&ccp_allowoptions[0].deflate },
"don't use draft deflate #", OPT_A2COPY,
&ccp_allowoptions[0].deflate_draft },
"don't allow Predictor-1", OPT_A2COPY,
&ccp_allowoptions[0].predictor_1 },
"don't allow Predictor-1", OPT_A2COPY,
&ccp_allowoptions[0].predictor_1 },
{ NULL }
};
/*
* Protocol entry points from main code.
*/
static void ccp_lowerdown __P((int));
void *arg));
1,
"CCP",
"Compressed",
NULL,
NULL,
};
/*
* Callbacks for fsm code.
*/
int len));
ccp_resetci, /* Reset our Configuration Information */
ccp_cilen, /* Length of our Configuration Information */
ccp_addci, /* Add our Configuration Information */
ccp_ackci, /* ACK our Configuration Information */
ccp_nakci, /* NAK our Configuration Information */
ccp_rejci, /* Reject our Configuration Information */
ccp_reqci, /* Request peer's Configuration Information */
ccp_up, /* Called when fsm reaches OPENED state */
ccp_down, /* Called when fsm leaves OPENED state */
NULL, /* Called when we want the lower layer up */
NULL, /* Called when we want the lower layer down */
NULL, /* Retransmission is necessary */
ccp_extcode, /* Called to handle LCP-specific codes */
"CCP", /* String name of protocol */
ccp_codereject, /* Peer rejected a code number */
};
/*
* Local statics.
*/
static void ccp_rack_timeout __P((void *));
/*
* Do we want / did we get any compression?
*/
/*
* Local state (mainly for handling reset-reqs and reset-acks).
*/
#ifdef COMP_TUNE
#endif
/*
* Option parsing.
*/
/*ARGSUSED*/
static int
char **argv;
{
}
return 0;
}
option_error("bsdcomp option values must be 0 or %d .. %d",
return 0;
}
if (rbits > 0) {
} else
ccp_wantoptions[0].bsd_compress = 0;
if (abits > 0) {
} else
ccp_allowoptions[0].bsd_compress = 0;
return 1;
}
/*ARGSUSED*/
static int
char **argv;
{
if (*str == ',')
else
if (*endp == ',') {
if (*str == ',')
else
}
#ifdef COMP_TUNE
}
#endif
return 0;
}
if (privileged_option) {
} else {
}
if (rbits < 0)
if (abits < 0)
option_error("deflate option values must be 0 or {%d,%d} .. {%d,%d}",
return 0;
}
if (privileged_option) {
}
if (rbits > 0) {
} else
ccp_wantoptions[0].deflate = 0;
if (abits > 0) {
} else
ccp_allowoptions[0].deflate = 0;
return 1;
}
/*
* ccp_init - initialize CCP.
*/
static void
int unit;
{
f->callbacks = &ccp_callbacks;
fsm_init(f);
f->flags |= OPT_RESTART;
}
/*
* ccp_open - CCP is allowed to come up.
*/
static void
int unit;
{
/*
* If we haven't gone open yet (first time through), then go open
* but not up. Otherwise, skip this to allow reopen to reset the
* compressor.
*/
/*
* Find out which compressors the kernel supports before
* deciding whether to open in silent mode.
*/
ccp_resetci(f);
f->flags |= OPT_SILENT;
fsm_open(f);
}
/*
* ccp_close - Terminate CCP.
*/
static void
int unit;
char *reason;
{
ccp_flags_set(unit, 0, 0);
}
/*
* ccp_lowerup - we may now transmit CCP packets.
*/
static void
int unit;
{
}
/*
* ccp_lowerdown - we may not transmit CCP packets.
*/
static void
int unit;
{
}
/*
* ccp_input - process a received CCP packet.
*/
static void
int unit;
u_char *p;
int len;
{
int oldstate;
/*
* Check for a terminate-request so we can print a message.
*/
notice("Compression disabled by peer.");
/*
* If we get a terminate-ack and we're not asking for compression,
* close CCP. (Terminate-Request is handled by fsm_input() above.)
*/
}
/*
* Handle a CCP-specific code.
*/
static int
fsm *f;
u_char *p;
int len;
{
switch (code) {
case CCP_RESETREQ:
/* If not open, then silently ignore. */
break;
/* send a reset-ack, which our transmitter module will see and
reset its compression state. */
break;
case CCP_RESETACK:
/*
* Note that the compression module isn't picky about ID
* numbers and such.
*/
}
break;
default:
/* Tell fsm to send code reject */
return (0);
}
return (1);
}
/*
* Handle Code-Reject for one of our extended codes by dropping back to
* reopen as mechanism to restart compression.
*/
/*ARGSUSED*/
static int
fsm *f;
int len;
{
switch (code) {
case CCP_RESETREQ:
info("peer has rejected CCP Reset-Request; falling back on Open");
}
return (0);
case CCP_RESETACK:
/*
* Peer must have sent us CCP Reset-Request but then code-rejected
* when we sent CCP Reset-Ack. He's a moron, be we have to obey
* his wishes.
*/
notice("peer has erroneously rejected CCP Reset-Ack");
f->term_reason = "peer sent Code-Reject for CCP Reset-Ack";
break;
default:
f->term_reason = "peer sent invalid Code-Reject";
break;
}
return (1);
}
/*
* ccp_protrej - peer doesn't talk CCP.
*/
static void
int unit;
{
/* Neither open nor up. */
ccp_flags_set(unit, 0, 0);
}
/*
* ccp_resetci - initialize at start of negotiation.
*/
static void
ccp_resetci(f)
fsm *f;
{
all_rejected[f->unit] = 0;
/*
* Check whether the kernel knows about the various
* decompression methods we might request.
*/
if (go->bsd_compress) {
opt_buf[0] = CI_BSD_COMPRESS;
go->bsd_compress = 0;
}
if (go->deflate_correct) {
opt_buf[0] = CI_DEFLATE;
go->deflate_correct = 0;
}
if (go->deflate_draft) {
opt_buf[0] = CI_DEFLATE_DRAFT;
go->deflate_draft = 0;
}
}
if (go->predictor_1) {
opt_buf[0] = CI_PREDICTOR_1;
go->predictor_1 = 0;
}
if (go->predictor_2) {
opt_buf[0] = CI_PREDICTOR_2;
go->predictor_2 = 0;
}
}
/*
* ccp_cilen - Return total length of our configuration info.
*/
static int
ccp_cilen(f)
fsm *f;
{
}
/*
* ccp_addci - put our requests in a packet.
*/
static void
fsm *f;
u_char *p;
int *lenp;
{
int res;
/*
* Add the compression types that we can receive, in decreasing
* preference order. Get the kernel to allocate the first one
* in case it gets Acked.
*/
p[1] = CILEN_DEFLATE;
p[3] = DEFLATE_CHK_SEQUENCE;
for (;;) {
if (res > 0) {
p += CILEN_DEFLATE;
break;
}
break;
}
--go->deflate_size;
}
/* If we're offering both, then this is second. */
p[0] = CI_DEFLATE_DRAFT;
p[1] = CILEN_DEFLATE;
p[3] = DEFLATE_CHK_SEQUENCE;
p += CILEN_DEFLATE;
}
}
if (go->bsd_compress) {
p[0] = CI_BSD_COMPRESS;
p[1] = CILEN_BSD_COMPRESS;
if (p != p0) {
p += CILEN_BSD_COMPRESS; /* not the first option */
} else {
for (;;) {
if (res > 0) {
p += CILEN_BSD_COMPRESS;
break;
}
go->bsd_compress = 0;
break;
}
}
}
}
/*
* Prefer Predictor-1 over Predictor-2. (The latter requires the use
* of LAP-B and has no known implementations.)
*/
if (go->predictor_1) {
p[0] = CI_PREDICTOR_1;
p[1] = CILEN_PREDICTOR_1;
go->predictor_1 = 0;
} else {
p += CILEN_PREDICTOR_1;
}
}
if (go->predictor_2) {
p[0] = CI_PREDICTOR_2;
p[1] = CILEN_PREDICTOR_2;
go->predictor_2 = 0;
} else {
p += CILEN_PREDICTOR_2;
}
}
}
/*
* ccp_ackci - process a received configure-ack, and return
* 1 iff the packet was OK.
*/
static int
fsm *f;
u_char *p;
int len;
{
if (len < CILEN_DEFLATE
|| p[3] != DEFLATE_CHK_SEQUENCE)
return 0;
return 1;
p += CILEN_DEFLATE;
len -= CILEN_DEFLATE;
}
if (len < CILEN_DEFLATE
|| p[3] != DEFLATE_CHK_SEQUENCE)
return 0;
return 1;
p += CILEN_DEFLATE;
len -= CILEN_DEFLATE;
}
if (go->bsd_compress) {
if (len < CILEN_BSD_COMPRESS
return 0;
return 1;
p += CILEN_BSD_COMPRESS;
}
if (go->predictor_1) {
if (len < CILEN_PREDICTOR_1
return 0;
return 1;
p += CILEN_PREDICTOR_1;
len -= CILEN_PREDICTOR_1;
}
if (go->predictor_2) {
if (len < CILEN_PREDICTOR_2
return 0;
return 1;
p += CILEN_PREDICTOR_2;
len -= CILEN_PREDICTOR_2;
}
/* Peer cannot ack something that wasn't sent. */
if (len != 0)
return 0;
return 1;
}
/*
* ccp_nakci - process received configure-nak.
* Returns 1 iff the nak was OK.
*/
static int
fsm *f;
u_char *p;
int len;
{
p[0] == CI_DEFLATE) {
/*
* Peer wants us to use a different code size or something.
* Stop asking for Deflate if we don't understand his suggestion.
*/
if (p[1] != CILEN_DEFLATE
|| p[3] != DEFLATE_CHK_SEQUENCE)
try.deflate_correct = 0;
len -= p[1];
p += p[1];
}
p[0] == CI_DEFLATE_DRAFT) {
/*
* Peer wants us to use a different code size or something.
* Stop asking for Deflate using the old algorithm number if
* we don't understand his suggestion. (Note that this will
* happen if the peer is running Magnalink instead of
* old-style Deflate.)
*/
if (p[1] != CILEN_DEFLATE
|| p[3] != DEFLATE_CHK_SEQUENCE)
try.deflate_draft = 0;
len -= p[1];
p += p[1];
}
p[0] == CI_BSD_COMPRESS) {
/*
* Peer wants us to use a different number of bits
* or a different version.
*/
if (p[1] != CILEN_BSD_COMPRESS ||
try.bsd_compress = 0;
len -= p[1];
p += p[1];
}
/*
* Predictor-1 and 2 have no options, so they can't be Naked.
*
* There may be remaining options but we ignore them.
*/
return 1;
}
/*
* ccp_rejci - peer rejects some of our suggested compression methods.
*/
static int
fsm *f;
u_char *p;
int len;
{
/*
* Cope with empty configure-rejects by ceasing to send
* configure-requests.
*/
return -1;
|| p[3] != DEFLATE_CHK_SEQUENCE)
return 0; /* Rej is bad */
try.deflate_correct = 0;
p += CILEN_DEFLATE;
len -= CILEN_DEFLATE;
}
|| p[3] != DEFLATE_CHK_SEQUENCE)
return 0; /* Rej is bad */
try.deflate_draft = 0;
p += CILEN_DEFLATE;
len -= CILEN_DEFLATE;
}
return 0;
try.bsd_compress = 0;
p += CILEN_BSD_COMPRESS;
}
try.predictor_1 = 0;
p += CILEN_PREDICTOR_1;
len -= CILEN_PREDICTOR_1;
}
try.predictor_2 = 0;
p += CILEN_PREDICTOR_2;
len -= CILEN_PREDICTOR_2;
}
if (len != 0)
return 0;
return 1;
}
/*
* ccp_reqci - process a received configure-request.
*
* Returns CODE_CONFACK, CODE_CONFNAK or CODE_CONFREJ and the packet
* is modified appropriately.
*/
static int
fsm *f;
u_char *p;
int *lenp;
int dont_nak;
{
ret = CODE_CONFACK;
nakp = nak_buffer;
/*
* RFC 1661 page 40 -- if the option extends beyond the
* packet, then discard the entire packet.
*/
return (0);
}
type = p[0];
clen = p[1];
pv = p;
switch (type) {
case CI_DEFLATE:
case CI_DEFLATE_DRAFT:
break;
}
if (clen != CILEN_DEFLATE ||
nb <= DEFLATE_MIN_SIZE) {
if (dont_nak)
break;
else if (nb <= DEFLATE_MIN_SIZE)
}
/*
* Check whether we can do Deflate with the window
* size they want. If the window is too big, reduce
* it until the kernel can cope and nak with that.
* We only check this for the first option.
*/
if (p == p0) {
for (;;) {
if (res > 0)
break; /* it's OK now */
break;
}
if (newret == CODE_CONFACK) {
nakp += CILEN_DEFLATE;
}
--nb;
}
#ifdef COMP_TUNE
/* Tune Deflate compression effort. */
if (newret == CODE_CONFACK)
#endif
}
break;
case CI_BSD_COMPRESS:
if (!ao->bsd_compress) {
break;
}
if (clen != CILEN_BSD_COMPRESS ||
if (dont_nak)
break;
else if (nb < BSD_MIN_BITS)
nb = BSD_MIN_BITS;
}
/*
* Check whether we can do BSD-Compress with the code
* size they want. If the code size is too big, reduce
* it until the kernel can cope and nak with that.
* We only check this for the first option.
*/
if (p == p0) {
for (;;) {
if (res > 0)
break;
break;
}
if (newret == CODE_CONFACK) {
}
--nb;
}
}
break;
case CI_PREDICTOR_1:
if (!ao->predictor_1) {
break;
}
if (clen != CILEN_PREDICTOR_1) {
if (dont_nak)
break;
}
if (p == p0 &&
}
break;
case CI_PREDICTOR_2:
if (!ao->predictor_2) {
break;
}
if (clen != CILEN_PREDICTOR_2) {
if (dont_nak)
break;
}
if (p == p0 &&
}
break;
default:
break;
}
/* Cope with confused peers. */
if (clen < 2)
clen = 2;
continue;
if (newret == CODE_CONFNAK) {
if (dont_nak) {
} else {
/* Ignore subsequent nakable things if rejecting. */
if (ret == CODE_CONFREJ)
continue;
ret = CODE_CONFNAK;
}
}
if (newret == CODE_CONFREJ) {
ret = CODE_CONFREJ;
if (p != rejp)
}
}
switch (ret) {
case CODE_CONFACK:
break;
case CODE_CONFNAK:
break;
case CODE_CONFREJ:
break;
}
return ret;
}
/*
* Make a string name for a compression method (or 2).
*/
static char *
{
if (!ANY_COMPRESS(*opt))
return "(none)";
case CI_DEFLATE:
case CI_DEFLATE_DRAFT:
else
opt->deflate_size);
break;
case CI_BSD_COMPRESS:
else
break;
case CI_PREDICTOR_1:
return "Predictor 1";
case CI_PREDICTOR_2:
return "Predictor 2";
#ifdef CI_STAC
case CI_STAC:
return "Stac";
#endif
#ifdef CI_MPPC
case CI_MPPC:
return "MS-PPC";
#endif
default:
}
return result;
}
/*
* CCP has come up - inform the kernel driver and log a message.
*/
static void
ccp_up(f)
fsm *f;
{
/*
* We're now open and up (running).
*/
if (ANY_COMPRESS(*go)) {
if (ANY_COMPRESS(*ho)) {
} else {
notice("%s / %s compression enabled",
}
} else
} else if (ANY_COMPRESS(*ho))
}
/*
* CCP has gone down - inform the kernel driver.
*/
static void
ccp_down(f)
fsm *f;
{
/* Don't forget about peer's code rejects or ignoring of requests. */
/* We're still open, but no longer up. */
}
static int
u_char *p;
int plen;
void *arg;
{
#ifdef CI_MPPC
#endif
p0 = p;
return (0);
}
return (HEADERLEN);
}
}
switch (code) {
case CODE_CONFREQ:
case CODE_CONFACK:
case CODE_CONFNAK:
case CODE_CONFREJ:
/* print list of possible compression methods */
while (len >= 2) {
if (optlen < 2)
optlen = 2;
switch (code) {
case CI_DEFLATE:
case CI_DEFLATE_DRAFT:
if (clen != CILEN_DEFLATE)
if (optlen >= CILEN_DEFLATE) {
if (cichar != DEFLATE_CHK_SEQUENCE)
}
break;
case CI_BSD_COMPRESS:
if (clen != CILEN_BSD_COMPRESS)
if (optlen >= CILEN_BSD_COMPRESS) {
}
break;
case CI_PREDICTOR_1:
if (clen != CILEN_PREDICTOR_1)
break;
case CI_PREDICTOR_2:
if (clen != CILEN_PREDICTOR_2)
break;
#ifdef CI_STAC
case CI_STAC:
if (clen != CILEN_STAC)
if (optlen >= CILEN_STAC) {
}
break;
#endif
#ifdef CI_MPPC
case CI_MPPC:
/* There appears to be no good generic name for this one. */
if (optlen >= CILEN_MPPC) {
else
} else {
else
}
} else {
}
if (clen != CILEN_STAC)
break;
#endif
default:
break;
}
if (p < optend) {
if (p+8 < optend)
else
p = optend;
}
}
break;
case CODE_TERMACK:
case CODE_TERMREQ:
if (len > 0) {
if (len == 2) {
len = 0;
} else if (*p >= ' ' && *p < 0x7f) {
p += len;
len = 0;
}
}
break;
}
/* dump out the rest of the packet in hex */
if (len > 0) {
if (len > 8)
else
p += len;
}
return p - p0;
}
/*
* We have received a packet that the decompressor failed to
* decompress. Here we would expect to issue a reset-request, but
* Motorola has a patent on resetting the compressor as a result of
* detecting an error in the decompressed data after decompression.
* (See US patent 5,130,993; international patent publication number
* WO 91/10289; Australian patent 73296/91.)
*
* So we ask the kernel whether the error was detected after
* decompression; if it was, we take CCP down, thus disabling
* compression :-(, otherwise we issue the reset-request.
*/
/*ARGSUSED*/
static void
int unit;
int len;
{
fsm *f;
if (ccp_fatal_error(unit)) {
/*
* Disable compression by taking CCP down.
*/
error("Lost compression sync: disabling compression");
} else {
/*
* Send a reset-request to reset the peer's compressor, if
* possible. We don't do anything if we are still waiting
* for an acknowledgement to a previous reset-request (to
* avoid flooding the peer). We reopen CCP if the peer
* doesn't like hearing about CCP Reset-Request (Cisco
* sends CCP Code-Reject for Reset-Request). (Reopen
* automatically clears the flags and cancels the
* timeout.)
*/
dbglog("reopening CCP to reset peer's compressor");
/* Send another reset request; we're out of sequence. */
} else {
dbglog("sending CCP Reset-Request to reset peer's compressor");
}
}
}
}
/*
* Timeout waiting for reset-ack.
*/
static void
void *arg;
{
/* Timeout; no longer pending. */
/* Frankly, it's a coding flaw if this occurs. */
return;
info("peer ignored our CCP Reset-Request twice; reopen instead");
ccp_localstate[f->unit] =
dbglog("sending another CCP Reset-Request on timeout");
ccp_localstate[f->unit] =
} else {
dbglog("timeout waiting for CCP Reset-Ack; hope for the best");
}
}