* ipcp.c - PPP IP Control Protocol.
* Copyright (c) 1989 Carnegie Mellon University.
* 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 Carnegie Mellon University. The name of the
* University 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.
static char rcsid
[] = "$Id: ipcp.c,v 1.2 1994/03/30 09:31:29 jkh Exp $";
ipcp_options ipcp_wantoptions
[_NPPP
]; /* Options that we want to request */
ipcp_options ipcp_gotoptions
[_NPPP
]; /* Options that peer ack'd */
ipcp_options ipcp_allowoptions
[_NPPP
]; /* Options we allow peer to request */
ipcp_options ipcp_hisoptions
[_NPPP
]; /* Options that we ack'd */
static int cis_received
[_NPPP
]; /* # Conf-Reqs received */
* Callbacks for fsm code. (CI = Configuration Information)
static void ipcp_resetci
__ARGS((fsm
*)); /* Reset our CI */
static int ipcp_cilen
__ARGS((fsm
*)); /* Return length of our CI */
static void ipcp_addci
__ARGS((fsm
*, u_char
*, int *)); /* Add our CI */
static int ipcp_ackci
__ARGS((fsm
*, u_char
*, int)); /* Peer ack'd our CI */
static int ipcp_nakci
__ARGS((fsm
*, u_char
*, int)); /* Peer nak'd our CI */
static int ipcp_rejci
__ARGS((fsm
*, u_char
*, int)); /* Peer rej'd our CI */
static int ipcp_reqci
__ARGS((fsm
*, u_char
*, int *, int)); /* Rcv CI */
static void ipcp_up
__ARGS((fsm
*)); /* We're UP */
static void ipcp_down
__ARGS((fsm
*)); /* We're DOWN */
fsm ipcp_fsm
[_NPPP
]; /* IPCP fsm structure */
static fsm_callbacks ipcp_callbacks
= { /* IPCP callback routines */
ipcp_resetci
, /* Reset our Configuration Information */
ipcp_cilen
, /* Length of our Configuration Information */
ipcp_addci
, /* Add our Configuration Information */
ipcp_ackci
, /* ACK our Configuration Information */
ipcp_nakci
, /* NAK our Configuration Information */
ipcp_rejci
, /* Reject our Configuration Information */
ipcp_reqci
, /* Request peer's Configuration Information */
ipcp_up
, /* Called when fsm reaches OPENED state */
ipcp_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
, /* Called when Protocol-Reject received */
NULL
, /* Retransmission is necessary */
NULL
, /* Called to handle protocol-specific codes */
"IPCP" /* String name of protocol */
* Lengths of configuration options.
#define CILEN_COMPRESS 4 /* min length for compression protocol opt. */
#define CILEN_VJ 6 /* length for RFC1332 Van-Jacobson opt. */
#define CILEN_ADDR 6 /* new-style single address option */
#define CILEN_ADDRS 10 /* old-style dual address option */
#define CODENAME(x) ((x) == CONFACK ? "ACK" : \
(x) == CONFNAK ? "NAK" : "REJ")
* Make a string representation of a network IP address.
sprintf(b
, "%d.%d.%d.%d",
* ipcp_init - Initialize IPCP.
fsm
*f
= &ipcp_fsm
[unit
];
ipcp_options
*wo
= &ipcp_wantoptions
[unit
];
ipcp_options
*ao
= &ipcp_allowoptions
[unit
];
f
->callbacks
= &ipcp_callbacks
;
fsm_init(&ipcp_fsm
[unit
]);
wo
->vj_protocol
= IPCP_VJ_COMP
;
wo
->maxslotindex
= MAX_STATES
- 1; /* really max index */
/* max slots and slot-id compression are currently hardwired in */
/* ppp_if.c to 16 and 1, this needs to be changed (among other */
ao
->maxslotindex
= MAX_STATES
- 1;
* ipcp_open - IPCP is allowed to come up.
fsm_open(&ipcp_fsm
[unit
]);
* ipcp_close - Take IPCP down.
fsm_close(&ipcp_fsm
[unit
]);
* ipcp_lowerup - The lower layer is up.
fsm_lowerup(&ipcp_fsm
[unit
]);
* ipcp_lowerdown - The lower layer is down.
fsm_lowerdown(&ipcp_fsm
[unit
]);
* ipcp_input - Input IPCP packet.
fsm_input(&ipcp_fsm
[unit
], p
, len
);
* ipcp_protrej - A Protocol-Reject was received for IPCP.
* Pretend the lower layer went down, so we shut up.
fsm_lowerdown(&ipcp_fsm
[unit
]);
* ipcp_resetci - Reset our CI.
ipcp_options
*wo
= &ipcp_wantoptions
[f
->unit
];
wo
->req_addr
= wo
->neg_addr
&& ipcp_allowoptions
[f
->unit
].neg_addr
;
ipcp_gotoptions
[f
->unit
] = *wo
;
cis_received
[f
->unit
] = 0;
* ipcp_cilen - Return length of our CI.
ipcp_options
*go
= &ipcp_gotoptions
[f
->unit
];
#define LENCIVJ(neg, old) (neg ? (old? CILEN_COMPRESS : CILEN_VJ) : 0)
#define LENCIADDR(neg, old) (neg ? (old? CILEN_ADDRS : CILEN_ADDR) : 0)
return (LENCIADDR(go
->neg_addr
, go
->old_addrs
) +
LENCIVJ(go
->neg_vj
, go
->old_vj
));
* ipcp_addci - Add our desired CIs to a packet.
ipcp_options
*wo
= &ipcp_wantoptions
[f
->unit
];
ipcp_options
*go
= &ipcp_gotoptions
[f
->unit
];
ipcp_options
*ho
= &ipcp_hisoptions
[f
->unit
];
#define ADDCIVJ(opt, neg, val, old, maxslotindex, cflag) \
int vjlen = old? CILEN_COMPRESS : CILEN_VJ; \
PUTCHAR(maxslotindex, ucp); \
#define ADDCIADDR(opt, neg, old, val1, val2) \
int addrlen = (old? CILEN_ADDRS: CILEN_ADDR); \
* First see if we want to change our options to the old
* forms because we have received old forms from the peer.
if (wo
->neg_addr
&& !go
->neg_addr
&& !go
->old_addrs
) {
/* use the old style of address negotiation */
if (wo
->neg_vj
&& !go
->neg_vj
&& !go
->old_vj
) {
/* try an older style of VJ negotiation */
if (cis_received
[f
->unit
] == 0) {
/* keep trying the new style until we see some CI from the peer */
/* use the old style only if the peer did */
if (ho
->neg_vj
&& ho
->old_vj
) {
go
->vj_protocol
= ho
->vj_protocol
;
ADDCIADDR((go
->old_addrs
? CI_ADDRS
: CI_ADDR
), go
->neg_addr
,
go
->old_addrs
, go
->ouraddr
, go
->hisaddr
);
ADDCIVJ(CI_COMPRESSTYPE
, go
->neg_vj
, go
->vj_protocol
, go
->old_vj
,
go
->maxslotindex
, go
->cflag
);
* ipcp_ackci - Ack our CIs.
ipcp_options
*go
= &ipcp_gotoptions
[f
->unit
];
u_short cilen
, citype
, cishort
;
u_char cimaxslotindex
, cicflag
;
* CIs must be in exactly the same order that we sent...
* Check packet length and CI length at each step.
* If we find any deviations, then this packet is bad.
#define ACKCIVJ(opt, neg, val, old, maxslotindex, cflag) \
int vjlen = old? CILEN_COMPRESS : CILEN_VJ; \
if ((len -= vjlen) < 0) \
GETCHAR(cimaxslotindex, p); \
if (cimaxslotindex != maxslotindex) \
#define ACKCIADDR(opt, neg, old, val1, val2) \
int addrlen = (old? CILEN_ADDRS: CILEN_ADDR); \
if ((len -= addrlen) < 0) \
if (cilen != addrlen || \
ACKCIADDR((go
->old_addrs
? CI_ADDRS
: CI_ADDR
), go
->neg_addr
,
go
->old_addrs
, go
->ouraddr
, go
->hisaddr
);
ACKCIVJ(CI_COMPRESSTYPE
, go
->neg_vj
, go
->vj_protocol
, go
->old_vj
,
go
->maxslotindex
, go
->cflag
);
* If there are any remaining CIs, then this packet is bad.
IPCPDEBUG((LOG_INFO
, "ipcp_ackci: received bad Ack!"));
* ipcp_nakci - Peer has sent a NAK for some of our CIs.
* This should not modify any state if the Nak is bad
* or if IPCP is in the OPENED state.
ipcp_options
*go
= &ipcp_gotoptions
[f
->unit
];
u_char cimaxslotindex
, cicflag
;
u_char citype
, cilen
, *next
;
u_long ciaddr1
, ciaddr2
, l
;
ipcp_options no
; /* options we've seen Naks for */
ipcp_options
try; /* options to request next time */
* Any Nak'd CIs must be in exactly the same order that we sent.
* Check packet length and CI length at each step.
* If we find any deviations, then this packet is bad.
#define NAKCIADDR(opt, neg, old, code) \
len >= (cilen = (old? CILEN_ADDRS: CILEN_ADDR)) && \
#define NAKCIVJ(opt, neg, code) \
((cilen = p[1]) == CILEN_COMPRESS || cilen == CILEN_VJ) && \
if (cilen == CILEN_VJ) { \
GETCHAR(cimaxslotindex, p); \
* Accept the peer's idea of {our,his} address, if different
* from our idea, only if the accept_{local,remote} flag is set.
NAKCIADDR(CI_ADDR
, neg_addr
, go
->old_addrs
,
if (go
->accept_local
&& ciaddr1
) { /* Do we know our address? */
IPCPDEBUG((LOG_INFO
, "local IP address %s",
if (go
->accept_remote
&& ciaddr2
) { /* Does he know his? */
IPCPDEBUG((LOG_INFO
, "remote IP address %s",
* Accept the peer's value of maxslotindex provided that it
* is less than what we asked for. Turn off slot-ID compression
* if the peer wants. Send old-style compress-type option if
NAKCIVJ(CI_COMPRESSTYPE
, neg_vj
,
if (cishort
== IPCP_VJ_COMP
) {
if (cimaxslotindex
< go
->maxslotindex
)
try.maxslotindex
= cimaxslotindex
;
if (cishort
== IPCP_VJ_COMP
|| cishort
== IPCP_VJ_COMP_OLD
) {
try.vj_protocol
= cishort
;
* There may be remaining CIs, if the peer is requesting negotiation
* on an option that we didn't include in our request packet.
* If they want to negotiate about IP addresses, we comply.
* If they want us to ask for compression, we refuse.
while (len
> CILEN_VOID
) {
if (go
->neg_vj
|| no
.neg_vj
||
(cilen
!= CILEN_VJ
&& cilen
!= CILEN_COMPRESS
))
if (go
->neg_addr
&& go
->old_addrs
|| no
.old_addrs
if (ciaddr1
&& go
->accept_local
)
if (ciaddr2
&& go
->accept_remote
)
if (go
->neg_addr
|| no
.neg_addr
|| cilen
!= CILEN_ADDR
)
if (ciaddr1
&& go
->accept_local
)
/* If there is still anything left, this packet is bad. */
* OK, the Nak is good. Now we can update state.
IPCPDEBUG((LOG_INFO
, "ipcp_nakci: received bad Nak!"));
* ipcp_rejci - Reject some of our CIs.
ipcp_options
*go
= &ipcp_gotoptions
[f
->unit
];
u_char cimaxslotindex
, ciflag
, cilen
;
ipcp_options
try; /* options to request next time */
* Any Rejected CIs must be in exactly the same order that we sent.
* Check packet length and CI length at each step.
* If we find any deviations, then this packet is bad.
#define REJCIADDR(opt, neg, old, val1, val2) \
len >= (cilen = old? CILEN_ADDRS: CILEN_ADDR) && \
/* Check rejected value. */ \
/* Check rejected value. */ \
#define REJCIVJ(opt, neg, val, old, maxslot, cflag) \
p[1] == (old? CILEN_COMPRESS : CILEN_VJ) && \
/* Check rejected value. */ \
GETCHAR(cimaxslotindex, p); \
if (cimaxslotindex != maxslot) \
REJCIADDR((go
->old_addrs
? CI_ADDRS
: CI_ADDR
), neg_addr
,
go
->old_addrs
, go
->ouraddr
, go
->hisaddr
);
REJCIVJ(CI_COMPRESSTYPE
, neg_vj
, go
->vj_protocol
, go
->old_vj
,
go
->maxslotindex
, go
->cflag
);
* If there are any remaining CIs, then this packet is bad.
* Now we can update state.
IPCPDEBUG((LOG_INFO
, "ipcp_rejci: received bad Reject!"));
* ipcp_reqci - Check the peer's requested CIs and send appropriate response.
* Returns: CONFACK, CONFNAK or CONFREJ and input packet modified
* appropriately. If reject_if_disagree is non-zero, doesn't return
* CONFNAK; returns CONFREJ if it can't return CONFACK.
ipcp_reqci(f
, inp
, len
, reject_if_disagree
)
u_char
*inp
; /* Requested CIs */
int *len
; /* Length of requested CIs */
ipcp_options
*wo
= &ipcp_wantoptions
[f
->unit
];
ipcp_options
*ho
= &ipcp_hisoptions
[f
->unit
];
ipcp_options
*ao
= &ipcp_allowoptions
[f
->unit
];
ipcp_options
*go
= &ipcp_gotoptions
[f
->unit
];
u_char
*cip
, *next
; /* Pointer to current and next CIs */
u_short cilen
, citype
; /* Parsed len, type */
u_short cishort
; /* Parsed short value */
u_long tl
, ciaddr1
, ciaddr2
;/* Parsed address values */
int rc
= CONFACK
; /* Final packet return code */
int orc
; /* Individual option return code */
u_char
*p
; /* Pointer to next char to parse */
u_char
*ucp
= inp
; /* Pointer to current output char */
int l
= *len
; /* Length left */
u_char maxslotindex
, cflag
;
* Process all his options.
orc
= CONFACK
; /* Assume success */
cip
= p
= next
; /* Remember begining of CI */
if (l
< 2 || /* Not enough data for CI header or */
p
[1] < 2 || /* CI length too small or */
p
[1] > l
) { /* CI length too big? */
IPCPDEBUG((LOG_INFO
, "ipcp_reqci: bad CI length!"));
orc
= CONFREJ
; /* Reject bad CI */
cilen
= l
; /* Reject till end of packet */
l
= 0; /* Don't loop again */
GETCHAR(citype
, p
); /* Parse CI type */
GETCHAR(cilen
, p
); /* Parse CI length */
l
-= cilen
; /* Adjust remaining length */
next
+= cilen
; /* Step to next CI */
switch (citype
) { /* Check CI type */
IPCPDEBUG((LOG_INFO
, "ipcp: received ADDRS "));
cilen
!= CILEN_ADDRS
) { /* Check CI length */
orc
= CONFREJ
; /* Reject CI */
* If he has no address, or if we both have his address but
* disagree about it, then NAK it with our idea.
* In particular, if we don't know his address, but he does,
GETLONG(tl
, p
); /* Parse source address (his) */
IPCPDEBUG((LOG_INFO
, "(%s:", ip_ntoa(ciaddr1
)));
if (ciaddr1
!= wo
->hisaddr
&& (ciaddr1
== 0 || !wo
->accept_remote
)) {
if (!reject_if_disagree
) {
DECPTR(sizeof (long), p
);
* If he doesn't know our address, or if we both have our address
* but disagree about it, then NAK it with our idea.
GETLONG(tl
, p
); /* Parse desination address (ours) */
IPCPDEBUG((LOG_INFO
, "%s)", ip_ntoa(ciaddr2
)));
if (ciaddr2
!= wo
->ouraddr
) {
if (ciaddr2
== 0 || !wo
->accept_local
) {
if (!reject_if_disagree
) {
DECPTR(sizeof (long), p
);
go
->ouraddr
= ciaddr2
; /* accept peer's idea */
IPCPDEBUG((LOG_INFO
, "ipcp: received ADDR "));
cilen
!= CILEN_ADDR
) { /* Check CI length */
orc
= CONFREJ
; /* Reject CI */
* If he has no address, or if we both have his address but
* disagree about it, then NAK it with our idea.
* In particular, if we don't know his address, but he does,
GETLONG(tl
, p
); /* Parse source address (his) */
IPCPDEBUG((LOG_INFO
, "(%s)", ip_ntoa(ciaddr1
)));
if (ciaddr1
!= wo
->hisaddr
&& (ciaddr1
== 0 || !wo
->accept_remote
)) {
if (!reject_if_disagree
) {
DECPTR(sizeof (long), p
);
IPCPDEBUG((LOG_INFO
, "ipcp: received COMPRESSTYPE "));
(cilen
!= CILEN_VJ
&& cilen
!= CILEN_COMPRESS
)) {
IPCPDEBUG((LOG_INFO
, "(%d)", cishort
));
if (!(cishort
== IPCP_VJ_COMP
||
(cishort
== IPCP_VJ_COMP_OLD
&& cilen
== CILEN_COMPRESS
))) {
ho
->vj_protocol
= cishort
;
GETCHAR(maxslotindex
, p
);
if (maxslotindex
> ao
->maxslotindex
) {
if (!reject_if_disagree
){
PUTCHAR(ao
->maxslotindex
, p
);
if (cflag
&& !ao
->cflag
) {
if (!reject_if_disagree
){
ho
->maxslotindex
= maxslotindex
;
IPCPDEBUG((LOG_INFO
, " (%s)\n", CODENAME(orc
)));
if (orc
== CONFACK
&& /* Good CI */
rc
!= CONFACK
) /* but prior CI wasnt? */
continue; /* Don't send this one */
if (orc
== CONFNAK
) { /* Nak this CI? */
if (reject_if_disagree
) /* Getting fed up with sending NAKs? */
orc
= CONFREJ
; /* Get tough if so */
if (rc
== CONFREJ
) /* Rejecting prior CI? */
continue; /* Don't send this one */
if (rc
== CONFACK
) { /* Ack'd all prior CIs? */
rc
= CONFNAK
; /* Not anymore... */
if (orc
== CONFREJ
&& /* Reject this CI */
rc
!= CONFREJ
) { /* but no prior ones? */
BCOPY(cip
, ucp
, cilen
); /* Move it */
/* Update output pointer */
* If we aren't rejecting this packet, and we want to negotiate
* their address, and they didn't send their address, then we
* send a NAK with a CI_ADDR option appended. We assume the
* input buffer is long enough that we can append the extra
if (rc
!= CONFREJ
&& !ho
->neg_addr
&&
wo
->req_addr
&& !reject_if_disagree
) {
ucp
= inp
; /* reset pointer */
wo
->req_addr
= 0; /* don't ask again */
PUTCHAR(CILEN_ADDR
, ucp
);
*len
= ucp
- inp
; /* Compute output length */
IPCPDEBUG((LOG_INFO
, "ipcp: returning Configure-%s", CODENAME(rc
)));
return (rc
); /* Return final code */
* ipcp_up - IPCP has come UP.
* Configure the IP network interface appropriately and bring it up.
ipcp_options
*ho
= &ipcp_hisoptions
[f
->unit
];
ipcp_options
*go
= &ipcp_gotoptions
[f
->unit
];
IPCPDEBUG((LOG_INFO
, "ipcp: up"));
* We must have a non-zero IP address for both ends of the link.
ho
->hisaddr
= ipcp_wantoptions
[f
->unit
].hisaddr
;
syslog(LOG_ERR
, "Could not determine remote IP address");
syslog(LOG_ERR
, "Could not determine local IP address");
* Check that the peer is allowed to use the IP address he wants.
if (!auth_ip_addr(f
->unit
, ho
->hisaddr
)) {
syslog(LOG_ERR
, "Peer is not authorized to use remote address %s",
syslog(LOG_NOTICE
, "local IP address %s", ip_ntoa(go
->ouraddr
));
syslog(LOG_NOTICE
, "remote IP address %s", ip_ntoa(ho
->hisaddr
));
* Set IP addresses and (if specified) netmask.
mask
= GetMask(go
->ouraddr
);
if (!sifaddr(f
->unit
, go
->ouraddr
, ho
->hisaddr
, mask
)) {
IPCPDEBUG((LOG_WARNING
, "sifaddr failed"));
/* set tcp compression */
sifvjcomp(f
->unit
, ho
->neg_vj
, ho
->cflag
);
/* bring the interface up for IP */
IPCPDEBUG((LOG_WARNING
, "sifup failed"));
/* assign a default route through the interface if required */
if (ipcp_wantoptions
[f
->unit
].default_route
)
if (sifdefaultroute(f
->unit
, ho
->hisaddr
))
/* Make a proxy ARP entry if requested. */
if (ipcp_wantoptions
[f
->unit
].proxy_arp
)
if (sifproxyarp(f
->unit
, ho
->hisaddr
))
* ipcp_down - IPCP has gone DOWN.
* Take the IP network interface down, clear its addresses
* and delete routes through it.
IPCPDEBUG((LOG_INFO
, "ipcp: down"));
ouraddr
= ipcp_gotoptions
[f
->unit
].ouraddr
;
hisaddr
= ipcp_hisoptions
[f
->unit
].hisaddr
;
if (ipcp_gotoptions
[f
->unit
].proxy_arp
)
cifproxyarp(f
->unit
, hisaddr
);
if (ipcp_gotoptions
[f
->unit
].default_route
)
cifdefaultroute(f
->unit
, hisaddr
);
cifaddr(f
->unit
, ouraddr
, hisaddr
);