/* * if_ppp.c - a network interface connected to a STREAMS module. * * 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. * * $Id$ */ /* * This file is used under SunOS 4 and Digital UNIX. * * This file provides the glue between PPP and IP. */ #define INET 1 #include #include #include #include #include #include #include #include #include #include #include #ifdef __osf__ #include #include #else #include #endif #include "ppp_mod.h" #include #ifdef SNIT_SUPPORT #include #include #include #endif #ifdef __osf__ #define SIOCSIFMTU SIOCSIPMTU #define SIOCGIFMTU SIOCRIPMTU #define IFA_ADDR(ifa) (*(ifa)->ifa_addr) #else #define IFA_ADDR(ifa) ((ifa)->ifa_addr) #endif #define ifr_mtu ifr_metric static int if_ppp_open __P((queue_t *, int, int, int)); static int if_ppp_close __P((queue_t *, int)); static int if_ppp_wput __P((queue_t *, mblk_t *)); static int if_ppp_rput __P((queue_t *, mblk_t *)); #define PPP_IF_ID 0x8021 static struct module_info minfo = { PPP_IF_ID, "if_ppp", 0, INFPSZ, 4096, 128 }; static struct qinit rinit = { if_ppp_rput, NULL, if_ppp_open, if_ppp_close, NULL, &minfo, NULL }; static struct qinit winit = { if_ppp_wput, NULL, NULL, NULL, NULL, &minfo, NULL }; struct streamtab if_pppinfo = { &rinit, &winit, NULL, NULL }; typedef struct if_ppp_state { int unit; queue_t *q; int flags; } if_ppp_t; /* Values for flags */ #define DBGLOG 1 static int if_ppp_count; /* Number of currently-active streams */ static int ppp_nalloc; /* Number of elements of ifs and states */ static struct ifnet **ifs; /* Array of pointers to interface structs */ static if_ppp_t **states; /* Array of pointers to state structs */ static int if_ppp_output __P((struct ifnet *, struct mbuf *, struct sockaddr *)); static int if_ppp_ioctl __P((struct ifnet *, u_int, caddr_t)); static struct mbuf *make_mbufs __P((mblk_t *, int)); static mblk_t *make_message __P((struct mbuf *, int)); #ifdef SNIT_SUPPORT /* Fake ether header for SNIT */ static struct ether_header snit_ehdr = {{0}, {0}, ETHERTYPE_IP}; #endif #ifndef __osf__ static void ppp_if_detach __P((struct ifnet *)); /* * Detach all the interfaces before unloading. * Not sure this works. */ int if_ppp_unload() { int i; if (if_ppp_count > 0) return EBUSY; for (i = 0; i < ppp_nalloc; ++i) if (ifs[i] != 0) ppp_if_detach(ifs[i]); if (ifs) { FREE(ifs, ppp_nalloc * sizeof (struct ifnet *)); FREE(states, ppp_nalloc * sizeof (struct if_ppp_t *)); } ppp_nalloc = 0; return 0; } #endif /* __osf__ */ /* * STREAMS module entry points. */ static int if_ppp_open(q, dev, flag, sflag) queue_t *q; int dev; int flag, sflag; { if_ppp_t *sp; if (q->q_ptr == 0) { sp = (if_ppp_t *) ALLOC_SLEEP(sizeof (if_ppp_t)); if (sp == 0) return OPENFAIL; bzero(sp, sizeof (if_ppp_t)); q->q_ptr = (caddr_t) sp; WR(q)->q_ptr = (caddr_t) sp; sp->unit = -1; /* no interface unit attached at present */ sp->q = WR(q); sp->flags = 0; ++if_ppp_count; } return 0; } static int if_ppp_close(q, flag) queue_t *q; int flag; { if_ppp_t *sp; struct ifnet *ifp; sp = (if_ppp_t *) q->q_ptr; if (sp != 0) { if (sp->flags & DBGLOG) printf("if_ppp closed, q=%x sp=%x\n", q, sp); if (sp->unit >= 0) { if (sp->unit < ppp_nalloc) { states[sp->unit] = 0; ifp = ifs[sp->unit]; if (ifp != 0) ifp->if_flags &= ~(IFF_UP | IFF_RUNNING); #ifdef DEBUG } else { printf("if_ppp: unit %d nonexistent!\n", sp->unit); #endif } } FREE(sp, sizeof (if_ppp_t)); --if_ppp_count; } return 0; } static int if_ppp_wput(q, mp) queue_t *q; mblk_t *mp; { if_ppp_t *sp; struct iocblk *iop; int error, unit; struct ifnet *ifp; sp = (if_ppp_t *) q->q_ptr; switch (mp->b_datap->db_type) { case M_DATA: /* * Now why would we be getting data coming in here?? */ if (sp->flags & DBGLOG) printf("if_ppp: got M_DATA len=%d\n", msgdsize(mp)); freemsg(mp); break; case M_IOCTL: iop = (struct iocblk *) mp->b_rptr; error = EINVAL; if (sp->flags & DBGLOG) printf("if_ppp: got ioctl cmd=%x count=%d\n", iop->ioc_cmd, iop->ioc_count); switch (iop->ioc_cmd) { case PPPIO_NEWPPA: /* well almost */ if (iop->ioc_count != sizeof(int) || sp->unit >= 0) break; if ((error = NOTSUSER()) != 0) break; unit = *(int *)mp->b_cont->b_rptr; /* Check that this unit isn't already in use */ if (unit < ppp_nalloc && states[unit] != 0) { error = EADDRINUSE; break; } /* Extend ifs and states arrays if necessary. */ error = ENOSR; if (unit >= ppp_nalloc) { int newn; struct ifnet **newifs; if_ppp_t **newstates; newn = unit + 4; if (sp->flags & DBGLOG) printf("if_ppp: extending ifs to %d\n", newn); newifs = (struct ifnet **) ALLOC_NOSLEEP(newn * sizeof (struct ifnet *)); if (newifs == 0) break; bzero(newifs, newn * sizeof (struct ifnet *)); newstates = (if_ppp_t **) ALLOC_NOSLEEP(newn * sizeof (struct if_ppp_t *)); if (newstates == 0) { FREE(newifs, newn * sizeof (struct ifnet *)); break; } bzero(newstates, newn * sizeof (struct if_ppp_t *)); bcopy(ifs, newifs, ppp_nalloc * sizeof(struct ifnet *)); bcopy(states, newstates, ppp_nalloc * sizeof(if_ppp_t *)); if (ifs) { FREE(ifs, ppp_nalloc * sizeof(struct ifnet *)); FREE(states, ppp_nalloc * sizeof(if_ppp_t *)); } ifs = newifs; states = newstates; ppp_nalloc = newn; } /* Allocate a new ifnet struct if necessary. */ ifp = ifs[unit]; if (ifp == 0) { ifp = (struct ifnet *) ALLOC_NOSLEEP(sizeof (struct ifnet)); if (ifp == 0) break; bzero(ifp, sizeof (struct ifnet)); ifs[unit] = ifp; ifp->if_name = "ppp"; ifp->if_unit = unit; ifp->if_mtu = PPP_MTU; ifp->if_flags = IFF_POINTOPOINT | IFF_RUNNING; #ifndef __osf__ #ifdef IFF_MULTICAST ifp->if_flags |= IFF_MULTICAST; #endif #endif /* __osf__ */ ifp->if_output = if_ppp_output; #ifdef __osf__ ifp->if_version = "Point-to-Point Protocol, version 2.3.11"; ifp->if_mediamtu = PPP_MTU; ifp->if_type = IFT_PPP; ifp->if_hdrlen = PPP_HDRLEN; ifp->if_addrlen = 0; ifp->if_flags |= IFF_NOARP | IFF_SIMPLEX | IFF_NOTRAILERS; #ifdef IFF_VAR_MTU ifp->if_flags |= IFF_VAR_MTU; #endif #ifdef NETMASTERCPU ifp->if_affinity = NETMASTERCPU; #endif #endif ifp->if_ioctl = if_ppp_ioctl; ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; if_attach(ifp); if (sp->flags & DBGLOG) printf("if_ppp: created unit %d\n", unit); } else { ifp->if_mtu = PPP_MTU; ifp->if_flags |= IFF_RUNNING; } states[unit] = sp; sp->unit = unit; error = 0; iop->ioc_count = 0; if (sp->flags & DBGLOG) printf("if_ppp: attached unit %d, sp=%x q=%x\n", unit, sp, sp->q); break; case PPPIO_DEBUG: error = -1; if (iop->ioc_count == sizeof(int)) { if (*(int *)mp->b_cont->b_rptr == PPPDBG_LOG + PPPDBG_IF) { printf("if_ppp: debug log enabled, q=%x sp=%x\n", q, sp); sp->flags |= DBGLOG; error = 0; iop->ioc_count = 0; } } break; default: error = -1; break; } if (sp->flags & DBGLOG) printf("if_ppp: ioctl result %d\n", error); if (error < 0) putnext(q, mp); else if (error == 0) { mp->b_datap->db_type = M_IOCACK; qreply(q, mp); } else { mp->b_datap->db_type = M_IOCNAK; iop->ioc_count = 0; iop->ioc_error = error; qreply(q, mp); } break; default: putnext(q, mp); } return 0; } static int if_ppp_rput(q, mp) queue_t *q; mblk_t *mp; { if_ppp_t *sp; int proto, s; struct mbuf *mb; struct ifqueue *inq; struct ifnet *ifp; int len; sp = (if_ppp_t *) q->q_ptr; switch (mp->b_datap->db_type) { case M_DATA: /* * Convert the message into an mbuf chain * and inject it into the network code. */ if (sp->flags & DBGLOG) printf("if_ppp: rput pkt len %d data %x %x %x %x %x %x %x %x\n", msgdsize(mp), mp->b_rptr[0], mp->b_rptr[1], mp->b_rptr[2], mp->b_rptr[3], mp->b_rptr[4], mp->b_rptr[5], mp->b_rptr[6], mp->b_rptr[7]); if (sp->unit < 0) { freemsg(mp); break; } if (sp->unit >= ppp_nalloc || (ifp = ifs[sp->unit]) == 0) { #ifdef DEBUG printf("if_ppp: no unit %d!\n", sp->unit); #endif freemsg(mp); break; } if ((ifp->if_flags & IFF_UP) == 0) { freemsg(mp); break; } ++ifp->if_ipackets; proto = PPP_PROTOCOL(mp->b_rptr); adjmsg(mp, PPP_HDRLEN); len = msgdsize(mp); mb = make_mbufs(mp, sizeof(struct ifnet *)); freemsg(mp); if (mb == NULL) { if (sp->flags & DBGLOG) printf("if_ppp%d: make_mbufs failed\n", ifp->if_unit); ++ifp->if_ierrors; break; } #ifdef SNIT_SUPPORT if (proto == PPP_IP && (ifp->if_flags & IFF_PROMISC)) { struct nit_if nif; nif.nif_header = (caddr_t) &snit_ehdr; nif.nif_hdrlen = sizeof(snit_ehdr); nif.nif_bodylen = len; nif.nif_promisc = 0; snit_intr(ifp, mb, &nif); } #endif /* * For Digital UNIX, there's space set aside in the header mbuf * for the interface info. * * For Sun it's smuggled around via a pointer at the front of the mbuf. */ #ifdef __osf__ mb->m_pkthdr.rcvif = ifp; mb->m_pkthdr.len = len; #else mb->m_off -= sizeof(struct ifnet *); mb->m_len += sizeof(struct ifnet *); *mtod(mb, struct ifnet **) = ifp; #endif inq = 0; switch (proto) { case PPP_IP: inq = &ipintrq; schednetisr(NETISR_IP); } if (inq != 0) { s = splhigh(); if (IF_QFULL(inq)) { IF_DROP(inq); ++ifp->if_ierrors; if (sp->flags & DBGLOG) printf("if_ppp: inq full, proto=%x\n", proto); m_freem(mb); } else { IF_ENQUEUE(inq, mb); } splx(s); } else { if (sp->flags & DBGLOG) printf("if_ppp%d: proto=%x?\n", ifp->if_unit, proto); ++ifp->if_ierrors; m_freem(mb); } break; default: putnext(q, mp); } return 0; } /* * Network code wants to output a packet. * Turn it into a STREAMS message and send it down. */ static int if_ppp_output(ifp, m0, dst) struct ifnet *ifp; struct mbuf *m0; struct sockaddr *dst; { mblk_t *mp; int proto, s; if_ppp_t *sp; u_char *p; if ((ifp->if_flags & IFF_UP) == 0) { m_freem(m0); return ENETDOWN; } if ((unsigned)ifp->if_unit >= ppp_nalloc) { #ifdef DEBUG printf("if_ppp_output: unit %d?\n", ifp->if_unit); #endif m_freem(m0); return EINVAL; } sp = states[ifp->if_unit]; if (sp == 0) { #ifdef DEBUG printf("if_ppp_output: no queue?\n"); #endif m_freem(m0); return ENETDOWN; } if (sp->flags & DBGLOG) { p = mtod(m0, u_char *); printf("if_ppp_output%d: af=%d data=%x %x %x %x %x %x %x %x q=%x\n", ifp->if_unit, dst->sa_family, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], sp->q); } switch (dst->sa_family) { case AF_INET: proto = PPP_IP; #ifdef SNIT_SUPPORT if (ifp->if_flags & IFF_PROMISC) { struct nit_if nif; struct mbuf *m; int len; for (len = 0, m = m0; m != NULL; m = m->m_next) len += m->m_len; nif.nif_header = (caddr_t) &snit_ehdr; nif.nif_hdrlen = sizeof(snit_ehdr); nif.nif_bodylen = len; nif.nif_promisc = 0; snit_intr(ifp, m0, &nif); } #endif break; default: m_freem(m0); return EAFNOSUPPORT; } ++ifp->if_opackets; mp = make_message(m0, PPP_HDRLEN); m_freem(m0); if (mp == 0) { ++ifp->if_oerrors; return ENOBUFS; } mp->b_rptr -= PPP_HDRLEN; mp->b_rptr[0] = PPP_ALLSTATIONS; mp->b_rptr[1] = PPP_UI; mp->b_rptr[2] = proto >> 8; mp->b_rptr[3] = proto; s = splstr(); if (sp->flags & DBGLOG) printf("if_ppp: putnext(%x, %x), r=%x w=%x p=%x\n", sp->q, mp, mp->b_rptr, mp->b_wptr, proto); putnext(sp->q, mp); splx(s); return 0; } /* * Socket ioctl routine for ppp interfaces. */ static int if_ppp_ioctl(ifp, cmd, data) struct ifnet *ifp; u_int cmd; caddr_t data; { int s, error; struct ifreq *ifr = (struct ifreq *) data; struct ifaddr *ifa = (struct ifaddr *) data; u_short mtu; error = 0; s = splimp(); switch (cmd) { case SIOCSIFFLAGS: if ((ifp->if_flags & IFF_RUNNING) == 0) ifp->if_flags &= ~IFF_UP; break; case SIOCSIFADDR: if (IFA_ADDR(ifa).sa_family != AF_INET) error = EAFNOSUPPORT; break; case SIOCSIFDSTADDR: if (IFA_ADDR(ifa).sa_family != AF_INET) error = EAFNOSUPPORT; break; case SIOCSIFMTU: if ((error = NOTSUSER()) != 0) break; #ifdef __osf__ /* this hack is necessary because ifioctl checks ifr_data * in 4.0 and 5.0, but ifr_data and ifr_metric overlay each * other in the definition of struct ifreq so pppd can't set both. */ bcopy(ifr->ifr_data, &mtu, sizeof (u_short)); ifr->ifr_mtu = mtu; #endif if (ifr->ifr_mtu < PPP_MINMTU || ifr->ifr_mtu > PPP_MAXMTU) { error = EINVAL; break; } ifp->if_mtu = ifr->ifr_mtu; break; case SIOCGIFMTU: ifr->ifr_mtu = ifp->if_mtu; break; case SIOCADDMULTI: case SIOCDELMULTI: switch(ifr->ifr_addr.sa_family) { case AF_INET: break; default: error = EAFNOSUPPORT; break; } break; default: error = EINVAL; } splx(s); return (error); } /* * Turn a STREAMS message into an mbuf chain. */ static struct mbuf * make_mbufs(mp, off) mblk_t *mp; int off; { struct mbuf *head, **prevp, *m; int len, space, n; unsigned char *cp, *dp; len = msgdsize(mp); if (len == 0) return 0; prevp = &head; space = 0; cp = mp->b_rptr; #ifdef __osf__ MGETHDR(m, M_DONTWAIT, MT_DATA); m->m_len = 0; space = MHLEN; *prevp = m; prevp = &m->m_next; dp = mtod(m, unsigned char *); len -= space; off = 0; #endif for (;;) { while (cp >= mp->b_wptr) { mp = mp->b_cont; if (mp == 0) { *prevp = 0; return head; } cp = mp->b_rptr; } n = mp->b_wptr - cp; if (space == 0) { MGET(m, M_DONTWAIT, MT_DATA); *prevp = m; if (m == 0) { if (head != 0) m_freem(head); return 0; } if (len + off > 2 * MLEN) { #ifdef __osf__ MCLGET(m, M_DONTWAIT); #else MCLGET(m); #endif } #ifdef __osf__ space = ((m->m_flags & M_EXT) ? MCLBYTES : MLEN); #else space = (m->m_off > MMAXOFF? MCLBYTES: MLEN) - off; m->m_off += off; #endif m->m_len = 0; len -= space; dp = mtod(m, unsigned char *); off = 0; prevp = &m->m_next; } if (n > space) n = space; bcopy(cp, dp, n); cp += n; dp += n; space -= n; m->m_len += n; } } /* * Turn an mbuf chain into a STREAMS message. */ #define ALLOCB_MAX 4096 static mblk_t * make_message(m, off) struct mbuf *m; int off; { mblk_t *head, **prevp, *mp; int len, space, n, nb; unsigned char *cp, *dp; struct mbuf *nm; len = 0; for (nm = m; nm != 0; nm = nm->m_next) len += nm->m_len; prevp = &head; space = 0; cp = mtod(m, unsigned char *); nb = m->m_len; for (;;) { while (nb <= 0) { m = m->m_next; if (m == 0) { *prevp = 0; return head; } cp = mtod(m, unsigned char *); nb = m->m_len; } if (space == 0) { space = len + off; if (space > ALLOCB_MAX) space = ALLOCB_MAX; mp = allocb(space, BPRI_LO); *prevp = mp; if (mp == 0) { if (head != 0) freemsg(head); return 0; } dp = mp->b_rptr += off; space -= off; len -= space; off = 0; prevp = &mp->b_cont; } n = nb < space? nb: space; bcopy(cp, dp, n); cp += n; dp += n; nb -= n; space -= n; mp->b_wptr = dp; } } /* * Digital UNIX doesn't allow for removing ifnet structures * from the list. But then we're not using this as a loadable * module anyway, so that's OK. * * Under SunOS, this should allow the module to be unloaded. * Unfortunately, it doesn't seem to detach all the references, * so your system may well crash after you unload this module :-( */ #ifndef __osf__ /* * Remove an interface from the system. * This routine contains magic. */ #include #include #include #include #include #include #include #include static void ppp_if_detach(ifp) struct ifnet *ifp; { int s; struct inpcb *pcb; struct ifaddr *ifa; struct in_ifaddr **inap; struct ifnet **ifpp; s = splhigh(); /* * Clear the interface from any routes currently cached in * TCP or UDP protocol control blocks. */ for (pcb = tcb.inp_next; pcb != &tcb; pcb = pcb->inp_next) if (pcb->inp_route.ro_rt && pcb->inp_route.ro_rt->rt_ifp == ifp) in_losing(pcb); for (pcb = udb.inp_next; pcb != &udb; pcb = pcb->inp_next) if (pcb->inp_route.ro_rt && pcb->inp_route.ro_rt->rt_ifp == ifp) in_losing(pcb); /* * Delete routes through all addresses of the interface. */ for (ifa = ifp->if_addrlist; ifa != 0; ifa = ifa->ifa_next) { rtinit(ifa, ifa, SIOCDELRT, RTF_HOST); rtinit(ifa, ifa, SIOCDELRT, 0); } /* * Unlink the interface's address(es) from the in_ifaddr list. */ for (inap = &in_ifaddr; *inap != 0; ) { if ((*inap)->ia_ifa.ifa_ifp == ifp) *inap = (*inap)->ia_next; else inap = &(*inap)->ia_next; } /* * Delete the interface from the ifnet list. */ for (ifpp = &ifnet; (*ifpp) != 0; ) { if (*ifpp == ifp) break; ifpp = &(*ifpp)->if_next; } if (*ifpp == 0) printf("couldn't find interface ppp%d in ifnet list\n", ifp->if_unit); else *ifpp = ifp->if_next; splx(s); } #endif /* __osf__ */