diff options
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.bin/pppd/sys-solaris.c')
-rw-r--r-- | usr/src/cmd/cmd-inet/usr.bin/pppd/sys-solaris.c | 3791 |
1 files changed, 3791 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/usr.bin/pppd/sys-solaris.c b/usr/src/cmd/cmd-inet/usr.bin/pppd/sys-solaris.c new file mode 100644 index 0000000000..9d11ac72d8 --- /dev/null +++ b/usr/src/cmd/cmd-inet/usr.bin/pppd/sys-solaris.c @@ -0,0 +1,3791 @@ +/* + * System-dependent procedures for pppd under Solaris 2.x (SunOS 5.x). + * + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * 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. + * + * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES + * + * 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" +#define RCSID "$Id: sys-solaris.c,v 1.2 2000/04/21 01:27:57 masputra Exp $" + +#include <limits.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <netdb.h> +#include <termios.h> +#include <signal.h> +#include <string.h> +#include <stropts.h> +#include <utmpx.h> +#include <sys/types.h> +#include <sys/ioccom.h> +#include <sys/stream.h> +#include <sys/stropts.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/sysmacros.h> +#include <sys/systeminfo.h> +#include <sys/dlpi.h> +#include <sys/stat.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/route.h> +#include <net/ppp_defs.h> +#include <net/pppio.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <netinet/in.h> +#include <sys/tihdr.h> +#include <inet/mib2.h> +#include <sys/ethernet.h> +#include <sys/ser_sync.h> + +#include "pppd.h" +#include "fsm.h" +#include "lcp.h" +#include "ipcp.h" +#ifdef INET6 +#include "ipv6cp.h" +#endif /* INET6 */ +#include "ccp.h" + +#if !defined(lint) && !defined(_lint) +static const char rcsid[] = RCSID; +#endif + +/* Need to use UDP for ifconfig compatibility */ +#if !defined(UDP_DEV_NAME) +#define UDP_DEV_NAME "/dev/udp" +#endif /* UDP_DEV_NAME */ + +#if !defined(IP_DEV_NAME) +#define IP_DEV_NAME "/dev/ip" +#endif /* IP_DEV_NAME */ + +#if !defined(UDP6_DEV_NAME) +#define UDP6_DEV_NAME "/dev/udp6" +#endif /* UDP6_DEV_NAME */ + +#if !defined(IP6_DEV_NAME) +#define IP6_DEV_NAME "/dev/ip6" +#endif /* IP6_DEV_NAME */ + +#if !defined(IP_MOD_NAME) +#define IP_MOD_NAME "ip" +#endif /* IP_MOD_NAME */ + +#define PPPSTRTIMOUT 1 /* Timeout in seconds for ioctl */ +#define MAX_POLLFDS 32 +#define NMODULES 32 + +#ifndef LIFNAMSIZ +#define LIFNAMSIZ 32 +#endif /* LIFNAMSIZ */ + +#ifndef MAXIFS +#define MAXIFS 256 +#endif /* MAXIFS */ + +#ifndef ETHERADDRL +#define ETHERADDRL 6 +#endif /* ETHERADDRL */ + +#ifdef INET6 +#define _IN6_LLX_FROM_EUI64(l, s, eui64, as, len) \ + (s->sin6_addr.s6_addr32[0] = htonl(as), \ + eui64_copy(eui64, s->sin6_addr.s6_addr32[2]), \ + s->sin6_family = AF_INET6, \ + l.lifr_addr.ss_family = AF_INET6, \ + l.lifr_addrlen = len, \ + l.lifr_addr = laddr) + +/* + * Generate a link-local address with an interface-id based on the given + * EUI64 identifier. Note that the len field is unused by SIOCSLIFADDR. + */ +#define IN6_LLADDR_FROM_EUI64(l, s, eui64) \ + _IN6_LLX_FROM_EUI64(l, s, eui64, 0xfe800000, 0) + +/* + * Generate an EUI64 based interface-id for use by stateless address + * autoconfiguration. These are required to be 64 bits long as defined in + * the "Interface Identifiers" section of the IPv6 Addressing Architecture + * (RFC3513). + */ +#define IN6_LLTOKEN_FROM_EUI64(l, s, eui64) \ + _IN6_LLX_FROM_EUI64(l, s, eui64, 0, 64) +#endif /* INET6 */ + +#define IPCP_ENABLED ipcp_protent.enabled_flag +#ifdef INET6 +#define IPV6CP_ENABLED ipv6cp_protent.enabled_flag +#endif /* INET6 */ + +/* For plug-in usage. */ +int (*sys_read_packet_hook) __P((int retv, struct strbuf *ctrl, + struct strbuf *data, int flags)) = NULL; +bool already_ppp = 0; /* Already in PPP mode */ + +static int pppfd = -1; /* ppp driver fd */ +static int fdmuxid = -1; /* driver mux fd */ +static int ipfd = -1; /* IPv4 fd */ +static int ipmuxid = -1; /* IPv4 mux fd */ +static int ip6fd = -1; /* IPv6 fd */ +static int ip6muxid = -1; /* IPv6 mux fd */ +static bool if6_is_up = 0; /* IPv6 if marked as up */ +static bool if_is_up = 0; /* IPv4 if marked as up */ +static bool restore_term = 0; /* Restore TTY after closing link */ +static struct termios inittermios; /* TTY settings */ +static struct winsize wsinfo; /* Initial window size info */ +static pid_t tty_sid; /* original sess ID for term */ +static struct pollfd pollfds[MAX_POLLFDS]; /* array of polled fd */ +static int n_pollfds = 0; /* total count of polled fd */ +static int link_mtu; /* link Maximum Transmit Unit */ +static int tty_nmodules; /* total count of TTY modules used */ +static char tty_modules[NMODULES][FMNAMESZ+1]; + /* array of TTY modules used */ +static int tty_npushed; /* total count of pushed PPP modules */ +static u_int32_t remote_addr; /* IP address of peer */ +static u_int32_t default_route_gateway; /* Gateway for default route */ +static u_int32_t proxy_arp_addr; /* Addr for proxy arp entry */ +static u_int32_t lastlink_status; /* Last link status info */ + +static bool use_plink = 0; /* Use I_LINK by default */ +static bool plumbed = 0; /* Use existing interface */ + +/* Default is to use /dev/sppp as driver. */ +static const char *drvnam = PPP_DEV_NAME; +static bool integrated_driver = 0; +static int extra_dev_fd = -1; /* keep open until ready */ + +static option_t solaris_option_list[] = { + { "plink", o_bool, &use_plink, "Use I_PLINK instead of I_LINK", + OPT_PRIV|1 }, + { "noplink", o_bool, &use_plink, "Use I_LINK instead of I_PLINK", + OPT_PRIV|0 }, + { "plumbed", o_bool, &plumbed, "Use pre-plumbed interface", + OPT_PRIV|1 }, + { NULL } +}; + +/* + * Prototypes for procedures local to this file. + */ +static int translate_speed __P((int)); +static int baud_rate_of __P((int)); +static int get_ether_addr __P((u_int32_t, struct sockaddr_dl *, int)); +static int dlpi_attach __P((int, int)); +static int dlpi_info_req __P((int)); +static int dlpi_get_reply __P((int, union DL_primitives *, int, int)); +static int strioctl __P((int, int, void *, int, int)); +static int plumb_ipif __P((int)); +static int unplumb_ipif __P((int)); +#ifdef INET6 +static int plumb_ip6if __P((int)); +static int unplumb_ip6if __P((int)); +static int open_ip6fd(void); +#endif /* INET6 */ +static int open_ipfd(void); +static int sifroute __P((int, u_int32_t, u_int32_t, int, const char *)); +static int giflags __P((u_int32_t, bool *)); +static void handle_unbind __P((u_int32_t)); +static void handle_bind __P((u_int32_t)); + +/* + * Wrapper for regular ioctl; masks out EINTR. + */ +static int +myioctl(int fd, int cmd, void *arg) +{ + int retv; + + errno = 0; + while ((retv = ioctl(fd, cmd, arg)) == -1) { + if (errno != EINTR) + break; + } + return (retv); +} + +/* + * sys_check_options() + * + * Check the options that the user specified. + */ +int +sys_check_options(void) +{ + if (plumbed) { + if (req_unit == -1) + req_unit = -2; + ipmuxid = 0; + ip6muxid = 0; + } + return (1); +} + +/* + * sys_options() + * + * Add or remove system-specific options. + */ +void +sys_options(void) +{ + (void) remove_option("ktune"); + (void) remove_option("noktune"); + add_options(solaris_option_list); +} + +/* + * sys_ifname() + * + * Set ifname[] to contain name of IP interface for this unit. + */ +void +sys_ifname(void) +{ + const char *cp; + + if ((cp = strrchr(drvnam, '/')) == NULL) + cp = drvnam; + else + cp++; + (void) slprintf(ifname, sizeof (ifname), "%s%d", cp, ifunit); +} + +/* + * ppp_available() + * + * Check whether the system has any ppp interfaces. + */ +int +ppp_available(void) +{ + struct stat buf; + int fd; + uint32_t typ; + + if (stat(PPP_DEV_NAME, &buf) >= 0) + return (1); + + /* + * Simple check for system using Apollo POS without SUNWpppd + * (/dev/sppp) installed. This is intentionally not kept open + * here, since the user may not have the same privileges (as + * determined later). If Apollo were just shipped with the + * full complement of packages, this wouldn't be an issue. + */ + if (devnam[0] == '\0' && + (fd = open(devnam, O_RDWR | O_NONBLOCK | O_NOCTTY)) >= 0) { + if (strioctl(fd, PPPIO_GTYPE, &typ, 0, sizeof (typ)) >= 0 && + typ == PPPTYP_MUX) { + (void) close(fd); + return (1); + } + (void) close(fd); + } + return (0); +} + +static int +open_ipfd(void) +{ + ipfd = open(IP_DEV_NAME, O_RDWR | O_NONBLOCK, 0); + if (ipfd < 0) { + error("Couldn't open IP device (%s): %m", IP_DEV_NAME); + } + return (ipfd); +} + +static int +read_ip_interface(int unit) +{ + struct ifreq ifr; + struct sockaddr_in sin; + + if (ipfd == -1 && open_ipfd() == -1) + return (0); + + BZERO(&ifr, sizeof (ifr)); + (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); + + /* Get the existing MTU */ + if (myioctl(ipfd, SIOCGIFMTU, &ifr) < 0) { + warn("Couldn't get IP MTU on %s: %m", ifr.ifr_name); + return (0); + } + dbglog("got MTU %d from interface", ifr.ifr_metric); + if (ifr.ifr_metric != 0 && + (lcp_allowoptions[unit].mru == 0 || + lcp_allowoptions[unit].mru > ifr.ifr_metric)) + lcp_allowoptions[unit].mru = ifr.ifr_metric; + + /* Get the local IP address */ + if (ipcp_wantoptions[unit].ouraddr == 0 || + ipcp_from_hostname) { + if (myioctl(ipfd, SIOCGIFADDR, &ifr) < 0) { + warn("Couldn't get local IP address (%s): %m", + ifr.ifr_name); + return (0); + } + BCOPY(&ifr.ifr_addr, &sin, sizeof (struct sockaddr_in)); + ipcp_wantoptions[unit].ouraddr = sin.sin_addr.s_addr; + dbglog("got local address %I from interface", + ipcp_wantoptions[unit].ouraddr); + } + + /* Get the remote IP address */ + if (ipcp_wantoptions[unit].hisaddr == 0) { + if (myioctl(ipfd, SIOCGIFDSTADDR, &ifr) < 0) { + warn("Couldn't get remote IP address (%s): %m", + ifr.ifr_name); + return (0); + } + BCOPY(&ifr.ifr_dstaddr, &sin, sizeof (struct sockaddr_in)); + ipcp_wantoptions[unit].hisaddr = sin.sin_addr.s_addr; + dbglog("got remote address %I from interface", + ipcp_wantoptions[unit].hisaddr); + } + return (1); +} + +#ifdef INET6 +static int +open_ip6fd(void) +{ + ip6fd = open(IP6_DEV_NAME, O_RDWR | O_NONBLOCK, 0); + if (ip6fd < 0) { + error("Couldn't open IPv6 device (%s): %m", IP6_DEV_NAME); + } + return (ip6fd); +} + +static int +read_ipv6_interface(int unit) +{ + struct lifreq lifr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; + + if (ip6fd == -1 && open_ip6fd() == -1) + return (0); + + BZERO(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + + /* Get the existing MTU */ + if (myioctl(ip6fd, SIOCGLIFMTU, &lifr) < 0) { + warn("Couldn't get IPv6 MTU on %s: %m", lifr.lifr_name); + return (0); + } + if (lifr.lifr_mtu != 0 && + (lcp_allowoptions[unit].mru == 0 || + lcp_allowoptions[unit].mru > lifr.lifr_mtu)) + lcp_allowoptions[unit].mru = lifr.lifr_mtu; + + /* Get the local IPv6 address */ + if (eui64_iszero(ipv6cp_wantoptions[unit].ourid) || + (ipcp_from_hostname && ipv6cp_wantoptions[unit].use_ip)) { + if (myioctl(ip6fd, SIOCGLIFADDR, &lifr) < 0) { + warn("Couldn't get local IPv6 address (%s): %m", + lifr.lifr_name); + return (0); + } + eui64_copy(sin6->sin6_addr.s6_addr32[2], + ipv6cp_wantoptions[unit].ourid); + } + + /* Get the remote IP address */ + if (eui64_iszero(ipv6cp_wantoptions[unit].hisid)) { + if (myioctl(ip6fd, SIOCGLIFDSTADDR, &lifr) < 0) { + warn("Couldn't get remote IPv6 address (%s): %m", + lifr.lifr_name); + return (0); + } + eui64_copy(sin6->sin6_addr.s6_addr32[2], + ipv6cp_wantoptions[unit].hisid); + } + return (1); +} +#endif /* INET6 */ + +/* + * Read information on existing interface(s) and configure ourselves + * to negotiate appropriately. + */ +static void +read_interface(int unit) +{ + dbglog("reading existing interface data; %sip %sipv6", + IPCP_ENABLED ? "" : "!", +#ifdef INET6 + IPV6CP_ENABLED ? "" : +#endif + "!"); + if (IPCP_ENABLED && !read_ip_interface(unit)) + IPCP_ENABLED = 0; +#ifdef INET6 + if (IPV6CP_ENABLED && !read_ipv6_interface(unit)) + IPV6CP_ENABLED = 0; +#endif +} + +/* + * sys_init() + * + * System-dependent initialization. + */ +void +sys_init(bool open_as_user) +{ + uint32_t x; + uint32_t typ; + + if (pppfd != -1) { + return; + } + + if (!direct_tty && devnam[0] != '\0') { + /* + * Check for integrated driver-like devices (such as + * POS). These identify themselves as "PPP + * multiplexor" drivers. + */ + if (open_as_user) + (void) seteuid(getuid()); + pppfd = open(devnam, O_RDWR | O_NONBLOCK); + if (open_as_user) + (void) seteuid(0); + if (pppfd >= 0 && + strioctl(pppfd, PPPIO_GTYPE, &typ, 0, sizeof (typ)) >= 0 && + typ == PPPTYP_MUX) { + integrated_driver = 1; + drvnam = devnam; + } else if (demand) { + (void) close(pppfd); + pppfd = -1; + } else { + extra_dev_fd = pppfd; + pppfd = -1; + } + } + + /* + * Open Solaris PPP device driver. + */ + if (pppfd < 0) + pppfd = open(drvnam, O_RDWR | O_NONBLOCK); + if (pppfd < 0) { + fatal("Can't open %s: %m", drvnam); + } + if (kdebugflag & 1) { + x = PPPDBG_LOG + PPPDBG_DRIVER; + if (strioctl(pppfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { + warn("PPPIO_DEBUG ioctl for mux failed: %m"); + } + } + /* + * Assign a new PPA and get its unit number. + */ + x = req_unit; + if (strioctl(pppfd, PPPIO_NEWPPA, &x, sizeof (x), sizeof (x)) < 0) { + if (errno == ENXIO && plumbed) + fatal("No idle interfaces available for use"); + fatal("PPPIO_NEWPPA ioctl failed: %m"); + } + ifunit = x; + if (req_unit >= 0 && ifunit != req_unit) { + if (plumbed) + fatal("unable to get requested unit %d", req_unit); + else + warn("unable to get requested unit %d", req_unit); + } + /* + * Enable packet time-stamping when idle option is specified. Note + * that we need to only do this on the control stream. Subsequent + * streams attached to this control stream (ppa) will inherit + * the time-stamp bit. + */ + if (idle_time_limit > 0) { + if (strioctl(pppfd, PPPIO_USETIMESTAMP, NULL, 0, 0) < 0) { + warn("PPPIO_USETIMESTAMP ioctl failed: %m"); + } + } + if (plumbed) { + sys_ifname(); + read_interface(0); + } +} + +int +sys_extra_fd(void) +{ + int fd; + + fd = extra_dev_fd; + extra_dev_fd = -1; + return (fd); +} + +static int +open_udpfd(void) +{ + int udpfd; + + udpfd = open(UDP_DEV_NAME, O_RDWR | O_NONBLOCK, 0); + if (udpfd < 0) { + error("Couldn't open UDP device (%s): %m", UDP_DEV_NAME); + } + return (udpfd); +} + +/* + * plumb_ipif() + * + * Perform IP interface plumbing. + */ +/*ARGSUSED*/ +static int +plumb_ipif(int unit) +{ + int udpfd = -1, tmpfd; + uint32_t x; + struct ifreq ifr; + + if (!IPCP_ENABLED || (ifunit == -1) || (pppfd == -1)) { + return (0); + } + if (plumbed) + return (1); + if (ipfd == -1 && open_ipfd() == -1) + return (0); + if (use_plink && (udpfd = open_udpfd()) == -1) + return (0); + tmpfd = open(drvnam, O_RDWR | O_NONBLOCK, 0); + if (tmpfd < 0) { + error("Couldn't open PPP device (%s): %m", drvnam); + if (udpfd != -1) + (void) close(udpfd); + return (0); + } + if (kdebugflag & 1) { + x = PPPDBG_LOG + PPPDBG_DRIVER; + if (strioctl(tmpfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { + warn("PPPIO_DEBUG ioctl for mux failed: %m"); + } + } + if (myioctl(tmpfd, I_PUSH, IP_MOD_NAME) < 0) { + error("Couldn't push IP module (%s): %m", IP_MOD_NAME); + goto err_ret; + } + /* + * Assign ppa according to the unit number returned by ppp device + * after plumbing is completed above. Without setting the ppa, ip + * module will return EINVAL upon setting the interface UP + * (SIOCSxIFFLAGS). This is because ip module in 2.8 expects two + * DLPI_INFO_REQ to be sent down to the driver (below ip) before + * IFF_UP bit can be set. Plumbing the device causes one DLPI_INFO_REQ + * to be sent down, and the second DLPI_INFO_REQ is sent upon receiving + * IF_UNITSEL (old) or SIOCSLIFNAME (new) ioctls. Such setting of the + * ppa is required because the ppp DLPI provider advertises itself as + * a DLPI style 2 type, which requires a point of attachment to be + * specified. The only way the user can specify a point of attachment + * is via SIOCSLIFNAME or IF_UNITSEL. Such changes in the behavior of + * ip module was made to meet new or evolving standards requirements. + */ + if (myioctl(tmpfd, IF_UNITSEL, &ifunit) < 0) { + error("Couldn't set ppa for unit %d: %m", ifunit); + goto err_ret; + } + if (use_plink) { + ipmuxid = myioctl(udpfd, I_PLINK, (void *)tmpfd); + if (ipmuxid < 0) { + error("Can't I_PLINK PPP device to IP: %m"); + goto err_ret; + } + } else { + ipmuxid = myioctl(ipfd, I_LINK, (void *)tmpfd); + if (ipmuxid < 0) { + error("Can't I_LINK PPP device to IP: %m"); + goto err_ret; + } + } + BZERO(&ifr, sizeof (ifr)); + (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); + ifr.ifr_ip_muxid = ipmuxid; + ifr.ifr_arp_muxid = -1; + if (myioctl(ipfd, SIOCSIFMUXID, (caddr_t)&ifr) < 0) { + error("Can't set mux ID SIOCSIFMUXID on %s: %m", ifname); + goto err_ret; + } + if (udpfd != -1) + (void) close(udpfd); + (void) close(tmpfd); + return (1); +err_ret: + if (udpfd != -1) + (void) close(udpfd); + (void) close(tmpfd); + return (0); +} + +/* + * unplumb_ipif() + * + * Perform IP interface unplumbing. Possibly called from die(), so there + * shouldn't be any call to die() or fatal() here. + */ +static int +unplumb_ipif(int unit) +{ + int udpfd = -1, fd = -1; + int id; + struct lifreq lifr; + + if (!IPCP_ENABLED || (ifunit == -1)) { + return (0); + } + if (!plumbed && (ipmuxid == -1 || (ipfd == -1 && !use_plink))) + return (1); + id = ipmuxid; + if (!plumbed && use_plink) { + if ((udpfd = open_udpfd()) == -1) + return (0); + /* + * Note: must re-get mux ID, since any intervening + * ifconfigs will change this. + */ + BZERO(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, + sizeof (lifr.lifr_name)); + if (myioctl(ipfd, SIOCGLIFMUXID, (caddr_t)&lifr) < 0) { + warn("Can't get mux fd: SIOCGLIFMUXID: %m"); + } else { + id = lifr.lifr_ip_muxid; + fd = myioctl(udpfd, _I_MUXID2FD, (void *)id); + if (fd < 0) { + warn("Can't get mux fd: _I_MUXID2FD: %m"); + } + } + } + /* + * Mark down and unlink the ip interface. + */ + (void) sifdown(unit); + if (default_route_gateway != 0) { + (void) cifdefaultroute(0, default_route_gateway, + default_route_gateway); + } + if (proxy_arp_addr != 0) { + (void) cifproxyarp(0, proxy_arp_addr); + } + ipmuxid = -1; + if (plumbed) + return (1); + if (use_plink) { + if (myioctl(udpfd, I_PUNLINK, (void *)id) < 0) { + error("Can't I_PUNLINK PPP from IP: %m"); + if (fd != -1) + (void) close(fd); + (void) close(udpfd); + return (0); + } + if (fd != -1) + (void) close(fd); + (void) close(udpfd); + } else { + if (myioctl(ipfd, I_UNLINK, (void *)id) < 0) { + error("Can't I_UNLINK PPP from IP: %m"); + return (0); + } + } + return (1); +} + +/* + * sys_cleanup() + * + * Restore any system state we modified before exiting: mark the + * interface down, delete default route and/or proxy arp entry. This + * should not call die() because it's called from die(). + */ +void +sys_cleanup() +{ + (void) unplumb_ipif(0); +#ifdef INET6 + (void) unplumb_ip6if(0); +#endif /* INET6 */ +} + +/* + * get_first_hwaddr() + * + * Stores the first hardware interface address found in the system + * into addr and return 1 upon success, or 0 if none is found. This + * is also called from the multilink code. + */ +int +get_first_hwaddr(addr, msize) + uchar_t *addr; + int msize; +{ + struct ifconf ifc; + register struct ifreq *pifreq; + struct ifreq ifr; + int fd, num_ifs, i; + uint_t fl, req_size; + char *req; + boolean_t found; + + if (addr == NULL) { + return (0); + } + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + error("get_first_hwaddr: error opening IP socket: %m"); + return (0); + } + /* + * Find out how many interfaces are running + */ + if (myioctl(fd, SIOCGIFNUM, (caddr_t)&num_ifs) < 0) { + num_ifs = MAXIFS; + } + req_size = num_ifs * sizeof (struct ifreq); + req = malloc(req_size); + if (req == NULL) { + novm("interface request structure."); + } + /* + * Get interface configuration info for all interfaces + */ + ifc.ifc_len = req_size; + ifc.ifc_buf = req; + if (myioctl(fd, SIOCGIFCONF, &ifc) < 0) { + error("SIOCGIFCONF: %m"); + (void) close(fd); + free(req); + return (0); + } + /* + * And traverse each interface to look specifically for the first + * occurence of an Ethernet interface which has been marked up + */ + pifreq = ifc.ifc_req; + found = 0; + for (i = ifc.ifc_len / sizeof (struct ifreq); i > 0; i--, pifreq++) { + + if (strchr(pifreq->ifr_name, ':') != NULL) { + continue; + } + BZERO(&ifr, sizeof (ifr)); + (void) strncpy(ifr.ifr_name, pifreq->ifr_name, + sizeof (ifr.ifr_name)); + if (myioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { + continue; + } + fl = ifr.ifr_flags; + if ((fl & (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT|IFF_LOOPBACK)) + != (IFF_UP | IFF_BROADCAST)) { + continue; + } + if (get_if_hwaddr(addr, msize, ifr.ifr_name) <= 0) { + continue; + } + found = 1; + break; + } + free(req); + (void) close(fd); + + return (found); +} + +/* + * get_if_hwaddr() + * + * Get the hardware address for the specified network interface device. + * Return the length of the MAC address (in bytes) or -1 if error. + */ +int +get_if_hwaddr(addr, msize, if_name) + uchar_t *addr; + int msize; + char *if_name; +{ + int unit, iffd, adrlen; + bool dlpi_err = 0; + char *adrp, *q; + char ifdev[4+LIFNAMSIZ+1]; /* take "/dev/" into account */ + struct { + union DL_primitives prim; + char space[64]; + } reply; + + if ((addr == NULL) || (if_name == NULL) || (if_name[0] == '\0')) { + return (-1); + } + /* + * We have to open the device and ask it for its hardware address. + * First split apart the device name and unit. + */ + (void) slprintf(ifdev, sizeof (ifdev), "/dev/%s", if_name); + for (q = ifdev + strlen(ifdev); --q >= ifdev; ) { + if (!isdigit(*q)) { + break; + } + } + unit = atoi(q + 1); + q[1] = '\0'; + /* + * Open the device and do a DLPI attach and phys_addr_req. + */ + iffd = open(ifdev, O_RDWR); + if (iffd < 0) { + error("Couldn't open %s: %m", ifdev); + return (-1); + } + + if (dlpi_attach(iffd, unit) < 0) { + error("DLPI attach to device %s failed", ifdev); + dlpi_err = 1; + } else if (dlpi_get_reply(iffd, &reply.prim, DL_OK_ACK, + sizeof (reply)) < 0) { + error("DLPI get attach reply on device %s failed", ifdev); + dlpi_err = 1; + } else if (dlpi_info_req(iffd) < 0) { + error("DLPI info request on device %s failed", ifdev); + dlpi_err = 1; + } else if (dlpi_get_reply(iffd, &reply.prim, DL_INFO_ACK, + sizeof (reply)) < 0) { + error("DLPI get info request reply on device %s failed", ifdev); + dlpi_err = 1; + } + (void) close(iffd); + iffd = -1; + if (dlpi_err) { + return (-1); + } + adrlen = reply.prim.info_ack.dl_addr_length; + adrp = (caddr_t)&reply + reply.prim.info_ack.dl_addr_offset; + + if (reply.prim.info_ack.dl_sap_length < 0) { + adrlen += reply.prim.info_ack.dl_sap_length; + } else { + adrp += reply.prim.info_ack.dl_sap_length; + } + /* + * Check if we have enough space to copy the address to. + */ + if (adrlen > msize) { + return (-1); + } + (void) memcpy(addr, adrp, adrlen); + return (adrlen); +} + +/* + * giflags() + */ +static int +giflags(u_int32_t flag, bool *retval) +{ + struct ifreq ifr; + int fd; + + *retval = 0; + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + error("giflags: error opening IP socket: %m"); + return (errno); + } + + BZERO(&ifr, sizeof (ifr)); + (void) strncpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); + if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { + (void) close(fd); + return (errno); + } + + *retval = ((ifr.ifr_flags & flag) != 0); + (void) close(fd); + return (errno); +} + +/* + * sys_close() + * + * Clean up in a child process before exec-ing. + */ +void +sys_close() +{ + if (ipfd != -1) { + (void) close(ipfd); + ipfd = -1; + } +#ifdef INET6 + if (ip6fd != -1) { + (void) close(ip6fd); + ip6fd = -1; + } +#endif /* INET6 */ + if (pppfd != -1) { + (void) close(pppfd); + pppfd = -1; + } +} + +/* + * any_compressions() + * + * Check if compression is enabled or not. In the STREAMS implementation of + * kernel-portion pppd, the comp STREAMS module performs the ACFC, PFC, as + * well CCP and VJ compressions. However, if the user has explicitly declare + * to not enable them from the command line, there is no point of having the + * comp module be pushed on the stream. + */ +static int +any_compressions(void) +{ + if ((!lcp_wantoptions[0].neg_accompression) && + (!lcp_wantoptions[0].neg_pcompression) && + (!ccp_protent.enabled_flag) && + (!ipcp_wantoptions[0].neg_vj)) { + return (0); + } + return (1); +} + +/* + * modpush() + * + * Push a module on the stream. + */ +static int +modpush(int fd, const char *modname, const char *text) +{ + if (myioctl(fd, I_PUSH, (void *)modname) < 0) { + error("Couldn't push %s module: %m", text); + return (-1); + } + if (++tty_npushed == 1 && !already_ppp) { + if (strioctl(fd, PPPIO_LASTMOD, NULL, 0, 0) < 0) { + warn("unable to set LASTMOD on %s: %m", text); + } + } + return (0); +} + +/* + * establish_ppp() + * + * Turn the serial port into a ppp interface. + */ +int +establish_ppp(fd) + int fd; +{ + int i; + uint32_t x; + + if (default_device && !notty) { + tty_sid = getsid((pid_t)0); + } + + if (integrated_driver) + return (pppfd); + + /* + * Pop any existing modules off the tty stream + */ + for (i = 0; ; ++i) { + if ((myioctl(fd, I_LOOK, tty_modules[i]) < 0) || + (strcmp(tty_modules[i], "ptem") == 0) || + (myioctl(fd, I_POP, (void *)0) < 0)) { + break; + } + } + tty_nmodules = i; + /* + * Push the async hdlc module and the compressor module + */ + tty_npushed = 0; + if (!sync_serial && !already_ppp && + modpush(fd, AHDLC_MOD_NAME, "PPP async HDLC") < 0) { + return (-1); + } + /* + * There's no need to push comp module if we don't intend + * to compress anything + */ + if (any_compressions()) { + (void) modpush(fd, COMP_MOD_NAME, "PPP compression"); + } + + /* + * Link the serial port under the PPP multiplexor + */ + if ((fdmuxid = myioctl(pppfd, I_LINK, (void *)fd)) < 0) { + error("Can't link tty to PPP mux: %m"); + return (-1); + } + if (tty_npushed == 0 && !already_ppp) { + if (strioctl(pppfd, PPPIO_LASTMOD, NULL, 0, 0) < 0) { + warn("unable to set LASTMOD on PPP mux: %m"); + } + } + /* + * Debug configuration must occur *after* I_LINK. + */ + if (kdebugflag & 4) { + x = PPPDBG_LOG + PPPDBG_AHDLC; + if (strioctl(pppfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { + warn("PPPIO_DEBUG ioctl for ahdlc module failed: %m"); + } + } + if (any_compressions() && (kdebugflag & 2)) { + x = PPPDBG_LOG + PPPDBG_COMP; + if (strioctl(pppfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { + warn("PPPIO_DEBUG ioctl for comp module failed: %m"); + } + } + return (pppfd); +} + +/* + * restore_loop() + * + * Reattach the ppp unit to the loopback. This doesn't need to do anything + * because disestablish_ppp does it + */ +void +restore_loop() +{ +} + +/* + * disestablish_ppp() + * + * Restore the serial port to normal operation. It attempts to reconstruct + * the stream with the previously popped modules. This shouldn't call die() + * because it's called from die(). Stream reconstruction is needed in case + * pppd is used for dial-in on /dev/tty and there's an option error. + */ +void +disestablish_ppp(fd) + int fd; +{ + int i; + + if (fdmuxid == -1 || integrated_driver) { + return; + } + if (myioctl(pppfd, I_UNLINK, (void *)fdmuxid) < 0) { + if (!hungup) { + error("Can't unlink tty from PPP mux: %m"); + } + } + fdmuxid = -1; + if (!hungup) { + while (tty_npushed > 0 && myioctl(fd, I_POP, (void *)0) >= 0) { + --tty_npushed; + } + for (i = tty_nmodules - 1; i >= 0; --i) { + if (myioctl(fd, I_PUSH, tty_modules[i]) < 0) { + error("Couldn't restore tty module %s: %m", + tty_modules[i]); + } + } + } + if (hungup && default_device && tty_sid > 0) { + /* + * If we have received a hangup, we need to send a + * SIGHUP to the terminal's controlling process. + * The reason is that the original stream head for + * the terminal hasn't seen the M_HANGUP message + * (it went up through the ppp driver to the stream + * head for our fd to /dev/ppp). + */ + (void) kill(tty_sid, SIGHUP); + } +} + +/* + * clean_check() + * + * Check whether the link seems not to be 8-bit clean + */ +void +clean_check() +{ + uint32_t x; + char *s = NULL; + + /* + * Skip this is synchronous link is used, since spppasyn won't + * be anywhere in the stream below to handle the ioctl. + */ + if (sync_serial) { + return; + } + + if (strioctl(pppfd, PPPIO_GCLEAN, &x, 0, sizeof (x)) < 0) { + warn("unable to obtain serial link status: %m"); + return; + } + switch (~x) { + case RCV_B7_0: + s = "bit 7 set to 1"; + break; + case RCV_B7_1: + s = "bit 7 set to 0"; + break; + case RCV_EVNP: + s = "odd parity"; + break; + case RCV_ODDP: + s = "even parity"; + break; + } + if (s != NULL) { + warn("Serial link is not 8-bit clean:"); + warn("All received characters had %s", s); + } +} + +/* + * List of valid speeds. + */ +struct speed { + int speed_int; + int speed_val; +} 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 B2000 + { 2000, B2000 }, +#endif +#ifdef B2400 + { 2400, B2400 }, +#endif +#ifdef B3600 + { 3600, B3600 }, +#endif +#ifdef B4800 + { 4800, B4800 }, +#endif +#ifdef B7200 + { 7200, B7200 }, +#endif +#ifdef B9600 + { 9600, B9600 }, +#endif +#ifdef B19200 + { 19200, B19200 }, +#endif +#ifdef B38400 + { 38400, B38400 }, +#endif +#ifdef EXTA + { 19200, EXTA }, +#endif +#ifdef EXTB + { 38400, EXTB }, +#endif +#ifdef B57600 + { 57600, B57600 }, +#endif +#ifdef B76800 + { 76800, B76800 }, +#endif +#ifdef B115200 + { 115200, B115200 }, +#endif +#ifdef B153600 + { 153600, B153600 }, +#endif +#ifdef B230400 + { 230400, B230400 }, +#endif +#ifdef B307200 + { 307200, B307200 }, +#endif +#ifdef B460800 + { 460800, B460800 }, +#endif + { 0, 0 } +}; + +/* + * translate_speed() + * + * Translate from bits/second to a speed_t + */ +static int +translate_speed(int bps) +{ + struct speed *speedp; + + if (bps == 0) { + return (0); + } + for (speedp = speeds; speedp->speed_int; speedp++) { + if (bps == speedp->speed_int) { + return (speedp->speed_val); + } + } + set_source(&speed_info); + option_error("speed %d not supported", bps); + return (0); +} + +/* + * baud_rate_of() + * + * Translate from a speed_t to bits/second + */ +static int +baud_rate_of(int speed) +{ + struct speed *speedp; + + if (speed == 0) { + return (0); + } + for (speedp = speeds; speedp->speed_int; speedp++) { + if (speed == speedp->speed_val) { + return (speedp->speed_int); + } + } + return (0); +} + +/* + * set_up_tty() + * + * Set up the serial port on `fd' for 8 bits, no parity, at the requested + * speed, etc. If `local' is true, set CLOCAL regardless of whether the + * modem option was specified. + */ +void +set_up_tty(fd, local) + int fd, local; +{ + int speed; + struct termios tios; + struct scc_mode sm; + + if (already_ppp) + return; + + if (sync_serial) { + restore_term = 0; + speed = B0; + baud_rate = 0; + + if (strioctl(fd, S_IOCGETMODE, &sm, sizeof (sm), + sizeof (sm)) < 0) { + return; + } + + baud_rate = sm.sm_baudrate; + dbglog("synchronous speed appears to be %d bps", baud_rate); + } else { + if (tcgetattr(fd, &tios) < 0) { + fatal("tcgetattr: %m"); + } + if (!restore_term) { + inittermios = tios; + if (myioctl(fd, TIOCGWINSZ, &wsinfo) < 0) { + if (errno == EINVAL) { + /* + * ptem returns EINVAL if all zeroes. + * Strange and unfixable code. + */ + bzero(&wsinfo, sizeof (wsinfo)); + } else { + warn("unable to get TTY window " + "size: %m"); + } + } + } + tios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CLOCAL); + if (crtscts > 0) { + tios.c_cflag |= CRTSCTS | CRTSXOFF; + } else if (crtscts < 0) { + tios.c_cflag &= ~CRTSCTS & ~CRTSXOFF; + } + tios.c_cflag |= CS8 | CREAD | HUPCL; + if (local || !modem) { + tios.c_cflag |= CLOCAL; + } + tios.c_iflag = IGNBRK | IGNPAR; + tios.c_oflag = 0; + tios.c_lflag = 0; + tios.c_cc[VMIN] = 1; + tios.c_cc[VTIME] = 0; + + if (crtscts == -2) { + tios.c_iflag |= IXON | IXOFF; + tios.c_cc[VSTOP] = 0x13; /* DC3 = XOFF = ^S */ + tios.c_cc[VSTART] = 0x11; /* DC1 = XON = ^Q */ + } + speed = translate_speed(inspeed); + if (speed) { + (void) cfsetospeed(&tios, speed); + (void) cfsetispeed(&tios, speed); + } else { + speed = cfgetospeed(&tios); + /* + * We can't proceed if the serial port speed is 0, + * since that implies that the serial port is disabled. + */ + if (speed == B0) { + fatal("Baud rate for %s is 0; need explicit " + "baud rate", devnam); + } + } + if (tcsetattr(fd, TCSAFLUSH, &tios) < 0) { + fatal("tcsetattr: %m"); + } + baud_rate = baud_rate_of(speed); + dbglog("%s speed set to %d bps", + fd == pty_slave ? "pty" : "serial", baud_rate); + restore_term = 1; + } +} + +/* + * restore_tty() + * + * Restore the terminal to the saved settings. + */ +void +restore_tty(fd) + int fd; +{ + if (restore_term == 0) { + return; + } + if (!default_device) { + /* + * Turn off echoing, because otherwise we can get into + * a loop with the tty and the modem echoing to each + * other. We presume we are the sole user of this tty + * device, so when we close it, it will revert to its + * defaults anyway. + */ + inittermios.c_lflag &= ~(ECHO | ECHONL); + } + if (tcsetattr(fd, TCSAFLUSH, &inittermios) < 0) { + if (!hungup && errno != ENXIO) { + warn("tcsetattr: %m"); + } + } + if (wsinfo.ws_row != 0 || wsinfo.ws_col != 0 || + wsinfo.ws_xpixel != 0 || wsinfo.ws_ypixel != 0) { + if (myioctl(fd, TIOCSWINSZ, &wsinfo) < 0) { + warn("unable to set TTY window size: %m"); + } + } + restore_term = 0; +} + +/* + * setdtr() + * + * Control the DTR line on the serial port. This is called from die(), so it + * shouldn't call die() + */ +void +setdtr(fd, on) + int fd, on; +{ + int modembits = TIOCM_DTR; + if (!already_ppp && + myioctl(fd, (on ? TIOCMBIS : TIOCMBIC), &modembits) < 0) { + warn("unable to set DTR line %s: %m", (on ? "ON" : "OFF")); + } +} + +/* + * open_loopback() + * + * Open the device we use for getting packets in demand mode. Under Solaris 2, + * we use our existing fd to the ppp driver. + */ +int +open_ppp_loopback() +{ + /* + * Plumb the interface. + */ + if (IPCP_ENABLED && (plumb_ipif(0) == 0)) { + fatal("Unable to initialize IP interface for demand dial."); + } +#ifdef INET6 + if (IPV6CP_ENABLED && (plumb_ip6if(0) == 0)) { + fatal("Unable to initialize IPv6 interface for demand dial."); + } +#endif /* INET6 */ + + return (pppfd); +} + +/* + * output() + * + * Output PPP packet downstream + */ +/*ARGSUSED*/ +void +output(unit, p, len) + int unit; + uchar_t *p; + int len; +{ + struct strbuf data; + struct pollfd pfd; + int retries, n; + bool sent_ok = 1; + + data.len = len; + data.buf = (caddr_t)p; + retries = 4; + + while (putmsg(pppfd, NULL, &data, 0) < 0) { + if (errno == EINTR) + continue; + if (--retries < 0 || + (errno != EWOULDBLOCK && errno != EAGAIN)) { + if (errno != ENXIO) { + error("Couldn't send packet: %m"); + sent_ok = 0; + } + break; + } + pfd.fd = pppfd; + pfd.events = POLLOUT; + do { + /* wait for up to 0.25 seconds */ + n = poll(&pfd, 1, 250); + } while ((n == -1) && (errno == EINTR)); + } + if (debug && sent_ok) { + dbglog("sent %P", p, len); + } +} + +/* + * wait_input() + * + * Wait until there is data available, for the length of time specified by + * timo (indefinite if timo is NULL). + */ +void +wait_input(timo) + struct timeval *timo; +{ + int t; + + t = (timo == NULL ? -1 : (timo->tv_sec * 1000 + timo->tv_usec / 1000)); + if ((poll(pollfds, n_pollfds, t) < 0) && (errno != EINTR)) { + fatal("poll: %m"); + } +} + +/* + * add_fd() + * + * Add an fd to the set that wait_input waits for. + */ +void +add_fd(fd) + int fd; +{ + int n; + + if (fd < 0) { + return; + } + for (n = 0; n < n_pollfds; ++n) { + if (pollfds[n].fd == fd) { + return; + } + } + if (n_pollfds < MAX_POLLFDS) { + pollfds[n_pollfds].fd = fd; + pollfds[n_pollfds].events = POLLIN | POLLPRI | POLLHUP; + ++n_pollfds; + } else { + fatal("add_fd: too many inputs!"); + } +} + +/* + * remove_fd() + * + * Remove an fd from the set that wait_input waits for. + */ +void +remove_fd(fd) + int fd; +{ + int n; + + for (n = 0; n < n_pollfds; ++n) { + if (pollfds[n].fd == fd) { + while (++n < n_pollfds) { + pollfds[n-1] = pollfds[n]; + } + --n_pollfds; + break; + } + } +} + +static void +dump_packet(uchar_t *buf, int len) +{ + uchar_t *bp; + int proto, offs; + const char *cp; + char sbuf[32]; + uint32_t src, dst; + struct protoent *pep; + + if (len < 4) { + dbglog("strange link activity: %.*B", len, buf); + return; + } + bp = buf; + if (bp[0] == 0xFF && bp[1] == 0x03) + bp += 2; + proto = *bp++; + if (!(proto & 1)) + proto = (proto << 8) + *bp++; + len -= bp-buf; + if (proto == PPP_IP) { + if (len < 20 || get_ipv(bp) != 4 || get_iphl(bp) < 5) { + dbglog("strange IP packet activity: %16.*B", len, buf); + return; + } + src = get_ipsrc(bp); + dst = get_ipdst(bp); + proto = get_ipproto(bp); + if ((pep = getprotobynumber(proto)) != NULL) { + cp = pep->p_name; + } else { + (void) slprintf(sbuf, sizeof (sbuf), "IP proto %d", + proto); + cp = sbuf; + } + if ((get_ipoff(bp) & IP_OFFMASK) != 0) { + len -= get_iphl(bp) * 4; + bp += get_iphl(bp) * 4; + dbglog("%s fragment from %I->%I: %8.*B", cp, src, dst, + len, bp); + } else { + if (len > get_iplen(bp)) + len = get_iplen(bp); + len -= get_iphl(bp) * 4; + bp += get_iphl(bp) * 4; + offs = proto == IPPROTO_TCP ? (get_tcpoff(bp)*4) : 8; + if (proto == IPPROTO_TCP || proto == IPPROTO_UDP) + dbglog("%s data:%d %I:%d->%I:%d: %8.*B", cp, + len-offs, src, get_sport(bp), dst, + get_dport(bp), len-offs, bp+offs); + else + dbglog("%s %d bytes %I->%I: %8.*B", cp, len, + src, dst, len, bp); + } + return; + } + if ((cp = protocol_name(proto)) == NULL) { + (void) slprintf(sbuf, sizeof (sbuf), "0x#X", proto); + cp = (const char *)sbuf; + } + dbglog("link activity: %s %16.*B", cp, len, bp); +} + +/* + * handle_bind() + */ +static void +handle_bind(u_int32_t reason) +{ + /* + * Here we might, in the future, handle DL_BIND_REQ notifications + * in order to close and re-open a NCP when certain interface + * parameters (addresses, etc.) are changed via external mechanisms + * such as through the "ifconfig" program. + */ + switch (reason) { + case PPP_LINKSTAT_IPV4_BOUND: + break; +#ifdef INET6 + case PPP_LINKSTAT_IPV6_BOUND: + break; +#endif + default: + error("handle_bind: unrecognized reason"); + break; + } +} + +/* + * handle_unbind() + */ +static void +handle_unbind(u_int32_t reason) +{ + bool iff_up_isset; + int rc; + static const char *unplumb_str = "unplumbed"; + static const char *down_str = "downed"; + + /* + * Since the kernel driver (sppp) notifies this daemon of the + * DLPI bind/unbind activities (for the purpose of bringing down + * a NCP), we need to explicitly test the "actual" status of + * the interface instance for which the notification is destined + * from. This is because /dev/ip performs multiple DLPI attach- + * bind-unbind-detach during the early life of the interface, + * and when certain interface parameters change. A DL_UNBIND_REQ + * coming down to the sppp driver from /dev/ip (which results in + * our receiving of the PPP_LINKSTAT_*_UNBOUND link status message) + * is not enough to conclude that the interface has been marked + * DOWN (its IFF_UP bit is cleared) or is going away. Therefore, + * we should query /dev/ip directly, upon receiving such *_UNBOUND + * notification, to determine whether the interface is DOWN + * for real, and only take the necessary actions when IFF_UP + * bit for the interface instance is actually cleared. + */ + switch (reason) { + case PPP_LINKSTAT_IPV4_UNBOUND: + (void) sleep(1); + rc = giflags(IFF_UP, &iff_up_isset); + if (!iff_up_isset) { + if_is_up = 0; + ipmuxid = -1; + info("IPv4 interface %s by administrator", + ((rc < 0 && rc == ENXIO) ? unplumb_str : down_str)); + fsm_close(&ipcp_fsm[0], + "administratively disconnected"); + } + break; +#ifdef INET6 + case PPP_LINKSTAT_IPV6_UNBOUND: + (void) sleep(1); + rc = giflags(IFF_UP, &iff_up_isset); + if (!iff_up_isset) { + if6_is_up = 0; + ip6muxid = -1; + info("IPv6 interface %s by administrator", + ((rc < 0 && rc == ENXIO) ? unplumb_str : down_str)); + fsm_close(&ipv6cp_fsm[0], + "administratively disconnected"); + } + break; +#endif + default: + error("handle_unbind: unrecognized reason"); + break; + } +} + +/* + * read_packet() + * + * Get a PPP packet from the serial device. + */ +int +read_packet(buf) + uchar_t *buf; +{ + struct strbuf ctrl; + struct strbuf data; + int flags; + int len; + int rc; + struct ppp_ls *plp; + uint32_t ctrlbuf[1536 / sizeof (uint32_t)]; + bool flushmode; + + flushmode = 0; + for (;;) { + + data.maxlen = PPP_MRU + PPP_HDRLEN; + data.buf = (caddr_t)buf; + + ctrl.maxlen = sizeof (ctrlbuf); + ctrl.buf = (caddr_t)ctrlbuf; + + flags = 0; + rc = len = getmsg(pppfd, &ctrl, &data, &flags); + if (sys_read_packet_hook != NULL) { + rc = len = (*sys_read_packet_hook)(len, &ctrl, &data, + flags); + } + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) { + return (-1); + } + fatal("Error reading packet: %m"); + } + if ((data.len > 0) && (ctrl.len < 0)) { + /* + * If there's more data on stream head, keep reading + * but discard, since the stream is now corrupt. + */ + if (rc & MOREDATA) { + dbglog("More data; input packet garbled"); + flushmode = 1; + continue; + } + if (flushmode) + return (-1); + return (data.len); + + } else if (ctrl.len > 0) { + /* + * If there's more ctl on stream head, keep reading, + * but start discarding. We can't deal with fragmented + * messages at all. + */ + if (rc & MORECTL) { + dbglog("More control; stream garbled"); + flushmode = 1; + continue; + } + if (flushmode) + return (-1); + if (ctrl.len < sizeof (struct ppp_ls)) { + warn("read_packet: ctl.len %d < " + "sizeof ppp_ls %d", + ctrl.len, sizeof (struct ppp_ls)); + return (-1); + } + plp = (struct ppp_ls *)ctrlbuf; + if (plp->magic != PPPLSMAGIC) { + /* Skip, as we don't understand it */ + dbglog("read_packet: unrecognized control %lX", + plp->magic); + return (-1); + } + + lastlink_status = plp->ppp_message; + + switch (plp->ppp_message) { + case PPP_LINKSTAT_HANGUP: + return (0); /* Hangup */ + /* For use by integrated drivers. */ + case PPP_LINKSTAT_UP: + lcp_lowerdown(0); + lcp_lowerup(0); + return (0); + case PPP_LINKSTAT_NEEDUP: + if (data.len > 0 && debug) + dump_packet(buf, data.len); + return (-1); /* Demand dial */ + case PPP_LINKSTAT_IPV4_UNBOUND: + (void) handle_unbind(plp->ppp_message); + return (-1); + case PPP_LINKSTAT_IPV4_BOUND: + (void) handle_bind(plp->ppp_message); + return (-1); +#ifdef INET6 + case PPP_LINKSTAT_IPV6_UNBOUND: + (void) handle_unbind(plp->ppp_message); + return (-1); + case PPP_LINKSTAT_IPV6_BOUND: + (void) handle_bind(plp->ppp_message); + return (-1); +#endif + default: + warn("read_packet: unknown link status type!"); + return (-1); + } + } else { + /* + * We get here on zero length data or control. + */ + return (-1); + } + } +} + +/* + * get_loop_output() + * + * Get outgoing packets from the ppp device, and detect when we want to bring + * the real link up. Return value is 1 if we need to bring up the link, or 0 + * otherwise. + */ +int +get_loop_output() +{ + int loops; + + /* + * In the Solaris 2.x kernel-level portion implementation, packets + * which are received on a demand-dial interface are immediately + * discarded, and a notification message is sent up the control + * stream to the pppd process. Therefore, the call to read_packet() + * below is merely there to wait for such message. + */ + lastlink_status = 0; + loops = 0; + while (read_packet(inpacket_buf) > 0) { + if (++loops > 10) + break; + } + return (lastlink_status == PPP_LINKSTAT_NEEDUP); +} + +#ifdef MUX_FRAME +/*ARGSUSED*/ +void +ppp_send_muxoption(unit, muxflag) + int unit; + u_int32_t muxflag; +{ + uint32_t cf[2]; + + /* + * Since muxed frame feature is implemented in the async module, + * don't send down the ioctl in the synchronous case. + */ + if (!sync_serial && fdmuxid >= 0 && pppfd != -1) { + cf[0] = muxflag; + cf[1] = X_MUXMASK; + + if (strioctl(pppfd, PPPIO_MUX, cf, sizeof (cf), 0) < 0) { + error("Couldn't set mux option: %m"); + } + } +} + +/*ARGSUSED*/ +void +ppp_recv_muxoption(unit, muxflag) + int unit; + u_int32_t muxflag; +{ + uint32_t cf[2]; + + /* + * Since muxed frame feature is implemented in the async module, + * don't send down the ioctl in the synchronous case. + */ + if (!sync_serial && fdmuxid >= 0 && pppfd != -1) { + cf[0] = muxflag; + cf[1] = R_MUXMASK; + + if (strioctl(pppfd, PPPIO_MUX, cf, sizeof (cf), 0) < 0) { + error("Couldn't set receive mux option: %m"); + } + } +} +#endif + +/* + * ppp_send_config() + * + * Configure the transmit characteristics of the ppp interface. + */ +/*ARGSUSED*/ +void +ppp_send_config(unit, mtu, asyncmap, pcomp, accomp) + int unit; + int mtu; + u_int32_t asyncmap; + int pcomp; + int accomp; +{ + uint32_t cf[2]; + + if (pppfd == -1) { + error("ppp_send_config called with invalid device handle"); + return; + } + cf[0] = link_mtu = mtu; + if (strioctl(pppfd, PPPIO_MTU, cf, sizeof (cf[0]), 0) < 0) { + if (hungup && errno == ENXIO) { + return; + } + error("Couldn't set MTU: %m"); + } + if (fdmuxid != -1) { + if (!sync_serial) { + if (strioctl(pppfd, PPPIO_XACCM, &asyncmap, + sizeof (asyncmap), 0) < 0) { + error("Couldn't set transmit ACCM: %m"); + } + } + cf[0] = (pcomp? COMP_PROT: 0) + (accomp? COMP_AC: 0); + cf[1] = COMP_PROT | COMP_AC; + + if (any_compressions() && strioctl(pppfd, PPPIO_CFLAGS, cf, + sizeof (cf), sizeof (cf[0])) < 0) { + error("Couldn't set prot/AC compression: %m"); + } + } +} + +/* + * ppp_set_xaccm() + * + * Set the extended transmit ACCM for the interface. + */ +/*ARGSUSED*/ +void +ppp_set_xaccm(unit, accm) + int unit; + ext_accm accm; +{ + if (sync_serial) { + return; + } + if (fdmuxid != -1 && strioctl(pppfd, PPPIO_XACCM, accm, + sizeof (ext_accm), 0) < 0) { + if (!hungup || errno != ENXIO) { + warn("Couldn't set extended ACCM: %m"); + } + } +} + +/* + * ppp_recv_config() + * + * Configure the receive-side characteristics of the ppp interface. + */ +/*ARGSUSED*/ +void +ppp_recv_config(unit, mru, asyncmap, pcomp, accomp) + int unit; + int mru; + u_int32_t asyncmap; + int pcomp; + int accomp; +{ + uint32_t cf[2]; + + if (pppfd == -1) { + error("ppp_recv_config called with invalid device handle"); + return; + } + cf[0] = mru; + if (strioctl(pppfd, PPPIO_MRU, cf, sizeof (cf[0]), 0) < 0) { + if (hungup && errno == ENXIO) { + return; + } + error("Couldn't set MRU: %m"); + } + if (fdmuxid != -1) { + if (!sync_serial) { + if (strioctl(pppfd, PPPIO_RACCM, &asyncmap, + sizeof (asyncmap), 0) < 0) { + error("Couldn't set receive ACCM: %m"); + } + } + cf[0] = (pcomp ? DECOMP_PROT : 0) + (accomp ? DECOMP_AC : 0); + cf[1] = DECOMP_PROT | DECOMP_AC; + + if (any_compressions() && strioctl(pppfd, PPPIO_CFLAGS, cf, + sizeof (cf), sizeof (cf[0])) < 0) { + error("Couldn't set prot/AC decompression: %m"); + } + } +} + +#ifdef NEGOTIATE_FCS +/* + * ppp_send_fcs() + * + * Configure the sender-side FCS. + */ +/*ARGSUSED*/ +void +ppp_send_fcs(unit, fcstype) + int unit, fcstype; +{ + uint32_t fcs; + + if (sync_serial) { + return; + } + + if (fcstype & FCSALT_32) { + fcs = PPPFCS_32; + } else if (fcstype & FCSALT_NULL) { + fcs = PPPFCS_NONE; + } else { + fcs = PPPFCS_16; + } + if (strioctl(pppfd, PPPIO_XFCS, &fcs, sizeof (fcs), 0) < 0) { + warn("Couldn't set transmit FCS: %m"); + } +} + +/* + * ppp_recv_fcs() + * + * Configure the receiver-side FCS. + */ +/*ARGSUSED*/ +void +ppp_recv_fcs(unit, fcstype) + int unit, fcstype; +{ + uint32_t fcs; + + if (sync_serial) { + return; + } + + if (fcstype & FCSALT_32) { + fcs = PPPFCS_32; + } else if (fcstype & FCSALT_NULL) { + fcs = PPPFCS_NONE; + } else { + fcs = PPPFCS_16; + } + if (strioctl(pppfd, PPPIO_RFCS, &fcs, sizeof (fcs), 0) < 0) { + warn("Couldn't set receive FCS: %m"); + } +} +#endif + +/* + * ccp_test() + * + * Ask kernel whether a given compression method is acceptable for use. + */ +/*ARGSUSED*/ +int +ccp_test(unit, opt_ptr, opt_len, for_transmit) + int unit; + uchar_t *opt_ptr; + int opt_len; + int for_transmit; +{ + if (strioctl(pppfd, (for_transmit ? PPPIO_XCOMP : PPPIO_RCOMP), + opt_ptr, opt_len, 0) >= 0) { + return (1); + } + warn("Error in %s ioctl: %m", + (for_transmit ? "PPPIO_XCOMP" : "PPPIO_RCOMP")); + return ((errno == ENOSR) ? 0 : -1); +} + +#ifdef COMP_TUNE +/* + * ccp_tune() + * + * Tune compression effort level. + */ +/*ARGSUSED*/ +void +ccp_tune(unit, effort) + int unit, effort; +{ + uint32_t x; + + x = effort; + if (strioctl(pppfd, PPPIO_COMPLEV, &x, sizeof (x), 0) < 0) { + warn("unable to set compression effort level: %m"); + } +} +#endif + +/* + * ccp_flags_set() + * + * Inform kernel about the current state of CCP. + */ +/*ARGSUSED*/ +void +ccp_flags_set(unit, isopen, isup) + int unit, isopen, isup; +{ + uint32_t cf[2]; + + cf[0] = (isopen ? CCP_ISOPEN : 0) + (isup ? CCP_ISUP : 0); + cf[1] = CCP_ISOPEN | CCP_ISUP | CCP_ERROR | CCP_FATALERROR; + + if (strioctl(pppfd, PPPIO_CFLAGS, cf, sizeof (cf), sizeof (cf[0])) + < 0) { + if (!hungup || errno != ENXIO) { + error("Couldn't set kernel CCP state: %m"); + } + } +} + +/* + * get_idle_time() + * + * Return how long the link has been idle. + */ +/*ARGSUSED*/ +int +get_idle_time(u, pids) + int u; + struct ppp_idle *pids; +{ + int rc; + + rc = strioctl(pppfd, PPPIO_GIDLE, pids, 0, sizeof (struct ppp_idle)); + if (rc < 0) { + warn("unable to obtain idle time: %m"); + } + return ((rc == 0) ? 1 : 0); +} + +/* + * get_ppp_stats() + * + * Return statistics for the link. + */ +/*ARGSUSED*/ +int +get_ppp_stats(u, stats) + int u; + struct pppd_stats *stats; +{ + struct ppp_stats64 s64; + struct ppp_stats s; + + /* Try first to get these from the 64-bit interface */ + if (strioctl(pppfd, PPPIO_GETSTAT64, &s64, 0, sizeof (s64)) >= 0) { + stats->bytes_in = s64.p.ppp_ibytes; + stats->bytes_out = s64.p.ppp_obytes; + stats->pkts_in = s64.p.ppp_ipackets; + stats->pkts_out = s64.p.ppp_opackets; + return (1); + } + + if (strioctl(pppfd, PPPIO_GETSTAT, &s, 0, sizeof (s)) < 0) { + error("Couldn't get link statistics: %m"); + return (0); + } + stats->bytes_in = s.p.ppp_ibytes; + stats->bytes_out = s.p.ppp_obytes; + stats->pkts_in = s.p.ppp_ipackets; + stats->pkts_out = s.p.ppp_opackets; + return (1); +} + +#if defined(FILTER_PACKETS) +/* + * set_filters() + * + * Transfer the pass and active filters to the kernel. + */ +int +set_filters(pass, active) + struct bpf_program *pass; + struct bpf_program *active; +{ + int ret = 1; + + if (pass->bf_len > 0) { + if (strioctl(pppfd, PPPIO_PASSFILT, pass, + sizeof (struct bpf_program), 0) < 0) { + error("Couldn't set pass-filter in kernel: %m"); + ret = 0; + } + } + if (active->bf_len > 0) { + if (strioctl(pppfd, PPPIO_ACTIVEFILT, active, + sizeof (struct bpf_program), 0) < 0) { + error("Couldn't set active-filter in kernel: %m"); + ret = 0; + } + } + return (ret); +} +#endif /* FILTER_PACKETS */ + +/* + * ccp_fatal_error() + * + * Returns 1 if decompression was disabled as a result of an error detected + * after decompression of a packet, 0 otherwise. This is necessary because + * of patent nonsense. + */ +/*ARGSUSED*/ +int +ccp_fatal_error(unit) + int unit; +{ + uint32_t cf[2]; + + cf[0] = cf[1] = 0; + if (strioctl(pppfd, PPPIO_CFLAGS, cf, sizeof (cf), sizeof (cf[0])) + < 0) { + if (errno != ENXIO && errno != EINVAL) { + error("Couldn't get compression flags: %m"); + } + return (0); + } + return (cf[0] & CCP_FATALERROR); +} + +/* + * sifvjcomp() + * + * Config TCP header compression. + */ +/*ARGSUSED*/ +int +sifvjcomp(u, vjcomp, xcidcomp, xmaxcid) + int u, vjcomp, xcidcomp, xmaxcid; +{ + uint32_t cf[2]; + uchar_t maxcid[2]; + + /* + * Since VJ compression code is in the comp module, there's no + * point of sending down any ioctls pertaining to VJ compression + * when the module isn't pushed on the stream. + */ + if (!any_compressions()) { + return (1); + } + + if (vjcomp) { + maxcid[0] = xcidcomp; + maxcid[1] = 15; /* XXX should be rmaxcid */ + + if (strioctl(pppfd, PPPIO_VJINIT, maxcid, + sizeof (maxcid), 0) < 0) { + error("Couldn't initialize VJ compression: %m"); + return (0); + } + } + + cf[0] = (vjcomp ? COMP_VJC + DECOMP_VJC : 0) /* XXX this is wrong */ + + (xcidcomp? COMP_VJCCID + DECOMP_VJCCID: 0); + + cf[1] = COMP_VJC + DECOMP_VJC + COMP_VJCCID + DECOMP_VJCCID; + + if (strioctl(pppfd, PPPIO_CFLAGS, cf, sizeof (cf), sizeof (cf[0])) + < 0) { + if (vjcomp) { + error("Couldn't enable VJ compression: %m"); + } else { + error("Couldn't disable VJ compression: %m"); + } + return (0); + } + return (1); +} + +/* + * siflags() + * + * Set or clear the IP interface flags. + */ +int +siflags(f, set) + u_int32_t f; + int set; +{ + struct ifreq ifr; + + if (!IPCP_ENABLED || (ipmuxid == -1)) { + return (0); + } + if (ipfd == -1 && open_ipfd() == -1) + return (0); + BZERO(&ifr, sizeof (ifr)); + (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); + if (myioctl(ipfd, SIOCGIFFLAGS, &ifr) < 0) { + error("Couldn't get IP interface flags: %m"); + return (0); + } + if (set) { + ifr.ifr_flags |= f; + } else { + ifr.ifr_flags &= ~f; + } + if (myioctl(ipfd, SIOCSIFFLAGS, &ifr) < 0) { + error("Couldn't set IP interface flags: %m"); + return (0); + } + return (1); +} + +/* + * sifup() + * + * Config the interface up and enable IP packets to pass. + */ +/*ARGSUSED*/ +int +sifup(u) + int u; +{ + if (if_is_up) { + return (1); + } else if (!IPCP_ENABLED) { + warn("sifup called when IPCP is disabled"); + return (0); + } else if (ipmuxid == -1) { + warn("sifup called in wrong state"); + return (0); + } else if (!siflags(IFF_UP, 1)) { + error("Unable to mark the IP interface UP"); + return (0); + } + if_is_up = 1; + return (1); +} + +/* + * sifdown() + * + * Config the interface down and disable IP. Possibly called from die(), + * so there shouldn't be any call to die() here. + */ +/*ARGSUSED*/ +int +sifdown(u) + int u; +{ + if (!IPCP_ENABLED) { + warn("sifdown called when IPCP is disabled"); + return (0); + } else if (!if_is_up || (ipmuxid == -1)) { + return (1); + } else if (!siflags(IFF_UP, 0)) { + error("Unable to mark the IP interface DOWN"); + return (0); + } + if_is_up = 0; + return (1); +} + +/* + * sifnpmode() + * + * Set the mode for handling packets for a given NP. Not worried + * about performance here since this is done only rarely. + */ +/*ARGSUSED*/ +int +sifnpmode(u, proto, mode) + int u; + int proto; + enum NPmode mode; +{ + uint32_t npi[2]; + const char *cp; + static const struct npi_entry { + enum NPmode ne_value; + const char *ne_name; + } npi_list[] = { + { NPMODE_PASS, "pass" }, + { NPMODE_DROP, "drop" }, + { NPMODE_ERROR, "error" }, + { NPMODE_QUEUE, "queue" }, + }; + int i; + char pname[32], mname[32]; + + npi[0] = proto; + npi[1] = (uint32_t)mode; + + cp = protocol_name(proto); + if (cp == NULL) + (void) slprintf(pname, sizeof (pname), "NP %04X", proto); + else + (void) strlcpy(pname, cp, sizeof (pname)); + for (i = 0; i < Dim(npi_list); i++) + if (npi_list[i].ne_value == mode) + break; + if (i >= Dim(npi_list)) + (void) slprintf(mname, sizeof (mname), "mode %d", (int)mode); + else + (void) strlcpy(mname, npi_list[i].ne_name, sizeof (mname)); + + if ((proto == PPP_IP && !if_is_up) || + (proto == PPP_IPV6 && !if6_is_up)) { + dbglog("ignoring request to set %s to %s", pname, mname); + return (1); + } + if (strioctl(pppfd, PPPIO_NPMODE, npi, sizeof (npi), 0) < 0) { + error("unable to set %s to %s: %m", pname, mname); + return (0); + } + return (1); +} + +/* + * sifmtu() + * + * Config the interface IP MTU. + */ +int +sifmtu(mtu) + int mtu; +{ + struct ifreq ifr; + + if (!IPCP_ENABLED || (ipmuxid == -1)) { + return (0); + } + if (ipfd == -1 && open_ipfd() == -1) + return (0); + BZERO(&ifr, sizeof (ifr)); + (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); + ifr.ifr_metric = mtu; + if (myioctl(ipfd, SIOCSIFMTU, &ifr) < 0) { + error("Couldn't set IP MTU on %s to %d: %m", ifr.ifr_name, + mtu); + return (0); + } + return (1); +} + +/* + * sifaddr() + * + * Config the interface IP addresses and netmask. + */ +/*ARGSUSED*/ +int +sifaddr(u, o, h, m) + int u; + u_int32_t o; + u_int32_t h; + u_int32_t m; +{ + struct ifreq ifr; + struct sockaddr_in sin; + + if (!IPCP_ENABLED || (ipmuxid == -1 && plumb_ipif(u) == 0)) { + return (0); + } + if (ipfd == -1 && open_ipfd() == -1) + return (0); + /* + * Set the IP interface MTU. + */ + if (!sifmtu(link_mtu)) { + return (0); + } + /* + * Set the IP interface local point-to-point address. + */ + BZERO(&sin, sizeof (sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = o; + + BZERO(&ifr, sizeof (ifr)); + (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); + ifr.ifr_addr = *(struct sockaddr *)&sin; + if (myioctl(ipfd, SIOCSIFADDR, &ifr) < 0) { + error("Couldn't set local IP address (%s): %m", ifr.ifr_name); + return (0); + } + /* + * Set the IP interface remote point-to-point address. + */ + sin.sin_addr.s_addr = h; + + ifr.ifr_dstaddr = *(struct sockaddr *)&sin; + if (myioctl(ipfd, SIOCSIFDSTADDR, &ifr) < 0) { + error("Couldn't set remote IP address (%s): %m", ifr.ifr_name); + return (0); + } + remote_addr = h; + return (1); +} + +/* + * cifaddr() + * + * Clear the interface IP addresses. + */ +/*ARGSUSED*/ +int +cifaddr(u, o, h) + int u; + u_int32_t o; + u_int32_t h; +{ + if (!IPCP_ENABLED) { + return (0); + } + /* + * Most of the work is done in sifdown(). + */ + remote_addr = 0; + return (1); +} + +/* + * sifroute() + * + * Add or delete a route. + */ +/*ARGSUSED*/ +static int +sifroute(int u, u_int32_t l, u_int32_t g, int add, const char *str) +{ + struct sockaddr_in sin_dst, sin_gtw; + struct rtentry rt; + + if (!IPCP_ENABLED || (ipmuxid == -1)) { + error("Can't %s route: IP is not enabled", str); + return (0); + } + if (ipfd == -1 && open_ipfd() == -1) + return (0); + + BZERO(&sin_dst, sizeof (sin_dst)); + sin_dst.sin_family = AF_INET; + sin_dst.sin_addr.s_addr = l; + + BZERO(&sin_gtw, sizeof (sin_gtw)); + sin_gtw.sin_family = AF_INET; + sin_gtw.sin_addr.s_addr = g; + + BZERO(&rt, sizeof (rt)); + rt.rt_dst = *(struct sockaddr *)&sin_dst; + rt.rt_gateway = *(struct sockaddr *)&sin_gtw; + rt.rt_flags = (RTF_GATEWAY|RTF_STATIC); + + if (myioctl(ipfd, (add ? SIOCADDRT : SIOCDELRT), &rt) < 0) { + error("Can't %s route: %m", str); + return (0); + } + return (1); +} + +/* + * sifdefaultroute() + * + * Assign a default route through the address given. + */ +/*ARGSUSED*/ +int +sifdefaultroute(u, l, g) + int u; + u_int32_t l; + u_int32_t g; +{ + if (!sifroute(u, 0, g, 1, "add default")) { + return (0); + } + default_route_gateway = g; + return (1); +} + +/* + * cifdefaultroute() + * + * Delete a default route through the address given. + */ +/*ARGSUSED*/ +int +cifdefaultroute(u, l, g) + int u; + u_int32_t l; + u_int32_t g; +{ + if (!sifroute(u, 0, g, 0, "delete default")) { + return (0); + } + default_route_gateway = 0; + return (1); +} + +/* + * sifproxyarp() + * + * Make a proxy ARP entry for the peer. + */ +/*ARGSUSED*/ +int +sifproxyarp(unit, hisaddr, quietflag) + int unit; + u_int32_t hisaddr; + int quietflag; +{ + struct sockaddr_in sin; + struct xarpreq arpreq; + const uchar_t *cp; + char *str = NULL; + + if (!IPCP_ENABLED || (ipmuxid == -1)) { + return (0); + } + if (ipfd == -1 && open_ipfd() == -1) + return (0); + + BZERO(&sin, sizeof (sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = hisaddr; + + BZERO(&arpreq, sizeof (arpreq)); + if (!get_ether_addr(hisaddr, &arpreq.xarp_ha, quietflag)) { + return (0); + } + BCOPY(&sin, &arpreq.xarp_pa, sizeof (sin)); + arpreq.xarp_flags = ATF_PERM | ATF_PUBL; + arpreq.xarp_ha.sdl_family = AF_LINK; + + if (myioctl(ipfd, SIOCSXARP, (caddr_t)&arpreq) < 0) { + if (!quietflag) + error("Couldn't set proxy ARP entry: %m"); + return (0); + } + cp = (const uchar_t *)LLADDR(&arpreq.xarp_ha); + str = _link_ntoa(cp, str, arpreq.xarp_ha.sdl_alen, IFT_OTHER); + if (str != NULL) { + dbglog("established proxy ARP for %I using %s", hisaddr, + str); + free(str); + } + proxy_arp_addr = hisaddr; + return (1); +} + +/* + * cifproxyarp() + * + * Delete the proxy ARP entry for the peer. + */ +/*ARGSUSED*/ +int +cifproxyarp(unit, hisaddr) + int unit; + u_int32_t hisaddr; +{ + struct sockaddr_in sin; + struct xarpreq arpreq; + + if (!IPCP_ENABLED || (ipmuxid == -1)) { + return (0); + } + if (ipfd == -1 && open_ipfd() == -1) + return (0); + + BZERO(&sin, sizeof (sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = hisaddr; + + BZERO(&arpreq, sizeof (arpreq)); + BCOPY(&sin, &arpreq.xarp_pa, sizeof (sin)); + arpreq.xarp_ha.sdl_family = AF_LINK; + + if (myioctl(ipfd, SIOCDXARP, (caddr_t)&arpreq) < 0) { + error("Couldn't delete proxy ARP entry: %m"); + return (0); + } + proxy_arp_addr = 0; + return (1); +} + +/* + * get_ether_addr() + * + * Get the hardware address of an interface on the the same subnet as + * ipaddr. This routine uses old-style interfaces for intentional + * backward compatibility -- SIOCGLIF* isn't in older Solaris + * releases. + */ +static int +get_ether_addr(u_int32_t ipaddr, struct sockaddr_dl *hwaddr, int quietflag) +{ + struct ifreq *ifr, *ifend, ifreq; + int nif, s, retv; + struct ifconf ifc; + u_int32_t ina, mask; + struct xarpreq req; + struct sockaddr_in sin; + + if (ipfd == -1 && open_ipfd() == -1) + return (0); + + /* + * Scan through the system's network interfaces. + */ + if (myioctl(ipfd, SIOCGIFNUM, &nif) < 0) { + nif = MAXIFS; + } + if (nif <= 0) + return (0); + ifc.ifc_len = nif * sizeof (struct ifreq); + ifc.ifc_buf = (caddr_t)malloc(ifc.ifc_len); + if (ifc.ifc_buf == NULL) { + return (0); + } + if (myioctl(ipfd, SIOCGIFCONF, &ifc) < 0) { + error("Couldn't get system interface list: %m"); + free(ifc.ifc_buf); + return (0); + } + /* LINTED */ + ifend = (struct ifreq *)(ifc.ifc_buf + ifc.ifc_len); + for (ifr = ifc.ifc_req; ifr < ifend; ++ifr) { + if (ifr->ifr_addr.sa_family != AF_INET) { + continue; + } + /* + * Check that the interface is up, and not + * point-to-point or loopback. + */ + (void) strlcpy(ifreq.ifr_name, ifr->ifr_name, + sizeof (ifreq.ifr_name)); + if (myioctl(ipfd, SIOCGIFFLAGS, &ifreq) < 0) { + continue; + } + if ((ifreq.ifr_flags & (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT| + IFF_LOOPBACK|IFF_NOARP)) != (IFF_UP|IFF_BROADCAST)) { + continue; + } + /* + * Get its netmask and check that it's on the right subnet. + */ + if (myioctl(ipfd, SIOCGIFNETMASK, &ifreq) < 0) { + continue; + } + (void) memcpy(&sin, &ifr->ifr_addr, sizeof (sin)); + ina = sin.sin_addr.s_addr; + (void) memcpy(&sin, &ifreq.ifr_addr, sizeof (sin)); + mask = sin.sin_addr.s_addr; + if ((ipaddr & mask) == (ina & mask)) { + break; + } + } + if (ifr >= ifend) { + if (!quietflag) + warn("No suitable interface found for proxy ARP of %I", + ipaddr); + free(ifc.ifc_buf); + return (0); + } + info("found interface %s for proxy ARP of %I", ifr->ifr_name, ipaddr); + + /* + * New way - get the address by doing an arp request. + */ + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + error("get_ether_addr: error opening IP socket: %m"); + free(ifc.ifc_buf); + return (0); + } + BZERO(&sin, sizeof (sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ina; + + BZERO(&req, sizeof (req)); + BCOPY(&sin, &req.xarp_pa, sizeof (sin)); + req.xarp_ha.sdl_family = AF_LINK; + + if (myioctl(s, SIOCGXARP, &req) < 0) { + error("Couldn't get ARP entry for %I: %m", ina); + retv = 0; + } else { + (void) memcpy(hwaddr, &req.xarp_ha, + sizeof (struct sockaddr_dl)); + retv = 1; + } + (void) close(s); + free(ifc.ifc_buf); + return (retv); +} + +/* + * dlpi_attach() + * + * Send down DL_ATTACH_REQ to driver. + */ +static int +dlpi_attach(int fd, int ppa) +{ + dl_attach_req_t req; + struct strbuf buf; + + if (fd < 0) { + return (-1); + } + BZERO(&req, sizeof (req)); + req.dl_primitive = DL_ATTACH_REQ; + req.dl_ppa = ppa; + + buf.len = sizeof (req); + buf.buf = (void *) &req; + + return (putmsg(fd, &buf, NULL, RS_HIPRI)); +} + +/* + * dlpi_info_req() + * + * Send down DL_INFO_REQ to driver. + */ +static int +dlpi_info_req(int fd) +{ + dl_info_req_t req; + struct strbuf buf; + + if (fd < 0) { + return (-1); + } + BZERO(&req, sizeof (req)); + req.dl_primitive = DL_INFO_REQ; + + buf.len = sizeof (req); + buf.buf = (void *) &req; + + return (putmsg(fd, &buf, NULL, RS_HIPRI)); +} + +/* + * dlpi_get_reply() + * + * Poll to get DLPI reply message from driver. + */ +static int +dlpi_get_reply(int fd, union DL_primitives *reply, int expected_prim, + int maxlen) +{ + struct strbuf buf; + struct pollfd pfd; + int flags; + int n; + + if (fd < 0) { + return (-1); + } + /* + * Use poll to wait for a message with a timeout. + */ + pfd.fd = fd; + pfd.events = (POLLIN | POLLPRI); + + do { + n = poll(&pfd, 1, 1000); + } while ((n == -1) && (errno == EINTR)); + + if (n <= 0) { + return (-1); + } + /* + * Get the reply. + */ + buf.maxlen = maxlen; + buf.buf = (void *)reply; + + flags = 0; + + if (getmsg(fd, &buf, NULL, &flags) < 0) { + return (-1); + } + if (buf.len < sizeof (ulong_t)) { + if (debug) { + dbglog("dlpi response short (len=%d)\n", buf.len); + } + return (-1); + } + if (reply->dl_primitive == expected_prim) { + return (0); + } + if (debug) { + if (reply->dl_primitive == DL_ERROR_ACK) { + dbglog("dlpi error %d (unix errno %d) for prim %x\n", + reply->error_ack.dl_errno, + reply->error_ack.dl_unix_errno, + reply->error_ack.dl_error_primitive); + } else { + dbglog("dlpi unexpected response prim %x\n", + reply->dl_primitive); + } + } + return (-1); +} + +/* + * GetMask() + * + * Return mask (bogus, but needed for compatibility with other platforms). + */ +/*ARGSUSED*/ +u_int32_t +GetMask(addr) + u_int32_t addr; +{ + return (0xffffffffUL); +} + +/* + * logwtmp() + * + * Write an accounting record to the /var/adm/wtmp file. + */ +/*ARGSUSED*/ +void +logwtmp(line, name, host) + const char *line; + const char *name; + const char *host; +{ + static struct utmpx utmpx; + + if (name[0] != '\0') { + /* + * logging in + */ + (void) strncpy(utmpx.ut_user, name, sizeof (utmpx.ut_user)); + (void) strncpy(utmpx.ut_id, ifname, sizeof (utmpx.ut_id)); + (void) strncpy(utmpx.ut_line, line, sizeof (utmpx.ut_line)); + + utmpx.ut_pid = getpid(); + utmpx.ut_type = USER_PROCESS; + } else { + utmpx.ut_type = DEAD_PROCESS; + } + (void) gettimeofday(&utmpx.ut_tv, NULL); + updwtmpx("/var/adm/wtmpx", &utmpx); +} + +/* + * get_host_seed() + * + * Return the serial number of this machine. + */ +int +get_host_seed() +{ + char buf[32]; + + if (sysinfo(SI_HW_SERIAL, buf, sizeof (buf)) < 0) { + error("sysinfo: %m"); + return (0); + } + return ((int)strtoul(buf, NULL, 16)); +} + +/* + * strioctl() + * + * Wrapper for STREAMS I_STR ioctl. Masks out EINTR from caller. + */ +static int +strioctl(int fd, int cmd, void *ptr, int ilen, int olen) +{ + struct strioctl str; + + str.ic_cmd = cmd; + str.ic_timout = PPPSTRTIMOUT; + str.ic_len = ilen; + str.ic_dp = ptr; + + if (myioctl(fd, I_STR, &str) == -1) { + return (-1); + } + if (str.ic_len != olen) { + dbglog("strioctl: expected %d bytes, got %d for cmd %x\n", + olen, str.ic_len, cmd); + } + return (0); +} + +/* + * have_route_to() + * + * Determine if the system has a route to the specified IP address. + * Returns 0 if not, 1 if so, -1 if we can't tell. `addr' is in network + * byte order. For demand mode to work properly, we have to ignore routes + * through our own interface. XXX Would be nice to use routing socket. + */ +int +have_route_to(addr) + u_int32_t addr; +{ + int r, flags, i; + struct { + struct T_optmgmt_req req; + struct opthdr hdr; + } req; + union { + struct T_optmgmt_ack ack; + unsigned char space[64]; + } ack; + struct opthdr *rh; + struct strbuf cbuf, dbuf; + int nroutes; + mib2_ipRouteEntry_t routes[8]; + mib2_ipRouteEntry_t *rp; + + if (ipfd == -1 && open_ipfd() == -1) + return (0); + + req.req.PRIM_type = T_OPTMGMT_REQ; + req.req.OPT_offset = (caddr_t)&req.hdr - (caddr_t)&req; + req.req.OPT_length = sizeof (req.hdr); +#ifdef T_CURRENT + req.req.MGMT_flags = T_CURRENT; +#else + /* Old-style */ + req.req.MGMT_flags = T_CHECK; +#endif + + req.hdr.level = MIB2_IP; + req.hdr.name = 0; + req.hdr.len = 0; + + cbuf.buf = (caddr_t)&req; + cbuf.len = sizeof (req); + + if (putmsg(ipfd, &cbuf, NULL, 0) == -1) { + warn("have_route_to: putmsg: %m"); + return (-1); + } + + for (;;) { + cbuf.buf = (caddr_t)&ack; + cbuf.maxlen = sizeof (ack); + dbuf.buf = (caddr_t)routes; + dbuf.maxlen = sizeof (routes); + flags = 0; + r = getmsg(ipfd, &cbuf, &dbuf, &flags); + if (r == -1) { + warn("have_route_to: getmsg: %m"); + return (-1); + } + + if (cbuf.len < sizeof (struct T_optmgmt_ack) || + ack.ack.PRIM_type != T_OPTMGMT_ACK || + ack.ack.MGMT_flags != T_SUCCESS || + ack.ack.OPT_length < sizeof (struct opthdr)) { + dbglog("have_route_to: bad message len=%d prim=%d", + cbuf.len, ack.ack.PRIM_type); + return (-1); + } + /* LINTED */ + rh = (struct opthdr *)((caddr_t)&ack + ack.ack.OPT_offset); + if (rh->level == 0 && rh->name == 0) { + break; + } + if (rh->level != MIB2_IP || rh->name != MIB2_IP_21) { + while (r == MOREDATA) { + r = getmsg(ipfd, NULL, &dbuf, &flags); + } + continue; + } + + /* + * Note that we have to skip routes to our own + * interface in order for demand dial to work. + * + * XXX awful hack here. We don't know our own + * ifIndex, so we can't check ipRouteIfIndex here. + * Instead, we check the next hop address. + */ + for (;;) { + nroutes = dbuf.len / sizeof (mib2_ipRouteEntry_t); + for (rp = routes, i = 0; i < nroutes; ++i, ++rp) { + if (rp->ipRouteNextHop != remote_addr && + ((addr ^ rp->ipRouteDest) & + rp->ipRouteMask) == 0) { + dbglog("have route to %I/%I via %I", + rp->ipRouteDest, + rp->ipRouteMask, + rp->ipRouteNextHop); + return (1); + } + } + if (r == 0) { + break; + } + r = getmsg(ipfd, NULL, &dbuf, &flags); + } + } + return (0); +} + +/* + * get_pty() + * + * Get a pty master/slave pair and chown the slave side to the uid given. + * Assumes slave_name points to MAXPATHLEN bytes of space. + */ +int +get_pty(master_fdp, slave_fdp, slave_name, uid) + int *master_fdp; + int *slave_fdp; + char *slave_name; + int uid; +{ + int mfd; + int sfd; + char *pty_name; + + mfd = open("/dev/ptmx", O_NOCTTY | O_RDWR); + if (mfd < 0) { + error("Couldn't open pty master: %m"); + return (0); + } + pty_name = ptsname(mfd); + if (pty_name == NULL) { + dbglog("Didn't get pty slave name on first try; sleeping."); + /* In case "grow" operation is in progress; try again. */ + (void) sleep(1); + pty_name = ptsname(mfd); + } + if (pty_name == NULL) { + error("Couldn't get name of pty slave"); + (void) close(mfd); + return (0); + } + if (chown(pty_name, uid, -1) < 0) { + warn("Couldn't change owner of pty slave: %m"); + } + if (chmod(pty_name, S_IRUSR | S_IWUSR) < 0) { + warn("Couldn't change permissions on pty slave: %m"); + } + if (unlockpt(mfd) < 0) { + warn("Couldn't unlock pty slave: %m"); + } + sfd = open(pty_name, O_RDWR); + if (sfd < 0) { + error("Couldn't open pty slave %s: %m", pty_name); + (void) close(mfd); + return (0); + } + if (myioctl(sfd, I_PUSH, "ptem") < 0) { + warn("Couldn't push ptem module on pty slave: %m"); + } + dbglog("Using %s; master fd %d, slave fd %d", pty_name, mfd, sfd); + + (void) strlcpy(slave_name, pty_name, MAXPATHLEN); + + *master_fdp = mfd; + *slave_fdp = sfd; + + return (1); +} + +#ifdef INET6 +static int +open_udp6fd(void) +{ + int udp6fd; + + udp6fd = open(UDP6_DEV_NAME, O_RDWR | O_NONBLOCK, 0); + if (udp6fd < 0) { + error("Couldn't open UDPv6 device (%s): %m", UDP6_DEV_NAME); + } + return (udp6fd); +} + +/* + * plumb_ip6if() + * + * Perform IPv6 interface plumbing. + */ +/*ARGSUSED*/ +static int +plumb_ip6if(int unit) +{ + int udp6fd = -1, tmpfd; + uint32_t x; + struct lifreq lifr; + + if (!IPV6CP_ENABLED || (ifunit == -1) || (pppfd == -1)) { + return (0); + } + if (plumbed) + return (1); + if (ip6fd == -1 && open_ip6fd() == -1) + return (0); + if (use_plink && (udp6fd = open_udp6fd()) == -1) + return (0); + tmpfd = open(drvnam, O_RDWR | O_NONBLOCK, 0); + if (tmpfd < 0) { + error("Couldn't open PPP device (%s): %m", drvnam); + if (udp6fd != -1) + (void) close(udp6fd); + return (0); + } + if (kdebugflag & 1) { + x = PPPDBG_LOG + PPPDBG_DRIVER; + if (strioctl(tmpfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { + warn("PPPIO_DEBUG ioctl for mux failed: %m"); + } + } + if (myioctl(tmpfd, I_PUSH, IP_MOD_NAME) < 0) { + error("Couldn't push IP module(%s): %m", IP_MOD_NAME); + goto err_ret; + } + /* + * Sets interface ppa and flags (refer to comments in plumb_ipif for + * the IF_UNITSEL ioctl). In addition, the IFF_IPV6 bit must be set in + * order to declare this as an IPv6 interface. + */ + BZERO(&lifr, sizeof (lifr)); + if (myioctl(tmpfd, SIOCGLIFFLAGS, &lifr) < 0) { + error("Couldn't get IPv6 interface flags: %m"); + goto err_ret; + } + lifr.lifr_flags |= IFF_IPV6; + lifr.lifr_flags &= ~(IFF_BROADCAST | IFF_IPV4); + lifr.lifr_ppa = ifunit; + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + if (myioctl(tmpfd, SIOCSLIFNAME, &lifr) < 0) { + error("Can't set ifname for unit %d: %m", ifunit); + goto err_ret; + } + if (use_plink) { + ip6muxid = myioctl(udp6fd, I_PLINK, (void *)tmpfd); + if (ip6muxid < 0) { + error("Can't I_PLINK PPP device to IPv6: %m"); + goto err_ret; + } + } else { + ip6muxid = myioctl(ip6fd, I_LINK, (void *)tmpfd); + if (ip6muxid < 0) { + error("Can't I_LINK PPP device to IPv6: %m"); + goto err_ret; + } + } + lifr.lifr_ip_muxid = ip6muxid; + lifr.lifr_arp_muxid = -1; + if (myioctl(ip6fd, SIOCSLIFMUXID, (caddr_t)&lifr) < 0) { + error("Can't set mux ID: SIOCSLIFMUXID: %m"); + goto err_ret; + } + (void) close(tmpfd); + if (udp6fd != -1) + (void) close(udp6fd); + return (1); + +err_ret: + (void) close(tmpfd); + if (udp6fd != -1) + (void) close(udp6fd); + return (0); +} + +/* + * unplumb_ip6if() + * + * Perform IPv6 interface unplumbing. Possibly called from die(), so there + * shouldn't be any call to die() here. + */ +static int +unplumb_ip6if(int unit) +{ + int udp6fd = -1, fd = -1; + int id; + struct lifreq lifr; + + if (!IPV6CP_ENABLED || ifunit == -1) { + return (0); + } + if (!plumbed && (ip6muxid == -1 || (ip6fd == -1 && !use_plink))) { + return (1); + } + id = ip6muxid; + if (!plumbed && use_plink) { + if ((udp6fd = open_udp6fd()) == -1) + return (0); + /* + * Note: must re-get mux ID, since any intervening + * ifconfigs will change this. + */ + BZERO(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, + sizeof (lifr.lifr_name)); + if (myioctl(ip6fd, SIOCGLIFMUXID, (caddr_t)&lifr) < 0) { + warn("Can't get mux fd: SIOCGLIFMUXID: %m"); + } else { + id = lifr.lifr_ip_muxid; + fd = myioctl(udp6fd, _I_MUXID2FD, (void *)id); + if (fd < 0) { + warn("Can't get mux fd: _I_MUXID2FD: %m"); + } + } + } + /* + * Mark down and unlink the IPv6 interface. + */ + (void) sif6down(unit); + if (plumbed) + return (1); + ip6muxid = -1; + if (use_plink) { + if ((fd = myioctl(udp6fd, _I_MUXID2FD, (void *)id)) < 0) { + error("Can't recapture mux fd: _I_MUXID2FD: %m"); + (void) close(udp6fd); + return (0); + } + if (myioctl(udp6fd, I_PUNLINK, (void *)id) < 0) { + error("Can't I_PUNLINK PPP from IPv6: %m"); + (void) close(fd); + (void) close(udp6fd); + return (0); + } + (void) close(fd); + (void) close(udp6fd); + } else { + if (myioctl(ip6fd, I_UNLINK, (void *)id) < 0) { + error("Can't I_UNLINK PPP from IPv6: %m"); + return (0); + } + } + return (1); +} + +/* + * sif6flags() + * + * Set or clear the IPv6 interface flags. + */ +int +sif6flags(f, set) + u_int32_t f; + int set; +{ + struct lifreq lifr; + int fd; + + if (!IPV6CP_ENABLED || (ip6muxid == -1)) { + return (0); + } + fd = socket(AF_INET6, SOCK_DGRAM, 0); + if (fd < 0) { + error("sif6flags: error opening IPv6 socket: %m"); + return (0); + } + BZERO(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + if (myioctl(fd, SIOCGLIFFLAGS, &lifr) < 0) { + error("Couldn't get IPv6 interface flags: %m"); + (void) close(fd); + return (0); + } + if (set) { + lifr.lifr_flags |= f; + } else { + lifr.lifr_flags &= ~f; + } + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + if (myioctl(fd, SIOCSLIFFLAGS, &lifr) < 0) { + error("Couldn't set IPv6 interface flags: %m"); + (void) close(fd); + return (0); + } + (void) close(fd); + return (1); +} + +/* + * sif6up() + * + * Config the IPv6 interface up and enable IPv6 packets to pass. + */ +/*ARGSUSED*/ +int +sif6up(unit) + int unit; +{ + if (if6_is_up) { + return (1); + } else if (!IPV6CP_ENABLED) { + warn("sif6up called when IPV6CP is disabled"); + return (0); + } else if (ip6muxid == -1) { + warn("sif6up called in wrong state"); + return (0); + } else if (!sif6flags(IFF_UP, 1)) { + error("Unable to mark the IPv6 interface UP"); + return (0); + } + if6_is_up = 1; + return (1); +} + +/* + * sif6down() + * + * Config the IPv6 interface down and disable IPv6. Possibly called from + * die(), so there shouldn't be any call to die() here. + */ +/*ARGSUSED*/ +int +sif6down(unit) + int unit; +{ + if (!IPV6CP_ENABLED) { + warn("sif6down called when IPV6CP is disabled"); + return (0); + } else if (!if6_is_up || (ip6muxid == -1)) { + return (1); + } else if (!sif6flags(IFF_UP, 0)) { + error("Unable to mark the IPv6 interface DOWN"); + return (0); + } + if6_is_up = 0; + return (1); +} + +/* + * sif6mtu() + * + * Config the IPv6 interface MTU. + */ +int +sif6mtu(mtu) + int mtu; +{ + struct lifreq lifr; + int s; + + if (!IPV6CP_ENABLED || (ip6muxid == -1)) { + return (0); + } + s = socket(AF_INET6, SOCK_DGRAM, 0); + if (s < 0) { + error("sif6mtu: error opening IPv6 socket: %m"); + return (0); + } + BZERO(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + lifr.lifr_mtu = mtu; + if (myioctl(s, SIOCSLIFMTU, &lifr) < 0) { + error("Couldn't set IPv6 MTU (%s): %m", lifr.lifr_name); + (void) close(s); + return (0); + } + (void) close(s); + return (1); +} + +/* + * sif6addr() + * + * Config the interface with an IPv6 link-local address. + */ +/*ARGSUSED*/ +int +sif6addr(unit, ourid, hisid) + int unit; + eui64_t ourid; + eui64_t hisid; +{ + struct lifreq lifr; + struct sockaddr_storage laddr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&laddr; + int fd; + + if (!IPV6CP_ENABLED || (ip6muxid == -1 && plumb_ip6if(unit) == 0)) { + return (0); + } + fd = socket(AF_INET6, SOCK_DGRAM, 0); + if (fd < 0) { + error("sif6addr: error opening IPv6 socket: %m"); + return (0); + } + /* + * Set the IPv6 interface MTU. + */ + if (!sif6mtu(link_mtu)) { + (void) close(fd); + return (0); + } + /* + * Set the interface address token. Do this because /dev/ppp responds + * to DL_PHYS_ADDR_REQ with zero values, hence the interface token + * came to be zero too, and without this, in.ndpd will complain. + */ + BZERO(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + BZERO(sin6, sizeof (struct sockaddr_in6)); + IN6_LLTOKEN_FROM_EUI64(lifr, sin6, ourid); + if (myioctl(fd, SIOCSLIFTOKEN, &lifr) < 0) { + error("Couldn't set IPv6 token (%s): %m", lifr.lifr_name); + (void) close(fd); + return (0); + } + /* + * Set the IPv6 interface local point-to-point address. + */ + IN6_LLADDR_FROM_EUI64(lifr, sin6, ourid); + if (myioctl(fd, SIOCSLIFADDR, &lifr) < 0) { + error("Couldn't set local IPv6 address (%s): %m", + lifr.lifr_name); + (void) close(fd); + return (0); + } + /* + * Set the IPv6 interface local point-to-point address. + */ + BZERO(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + IN6_LLADDR_FROM_EUI64(lifr, sin6, hisid); + if (myioctl(fd, SIOCSLIFDSTADDR, &lifr) < 0) { + error("Couldn't set remote IPv6 address (%s): %m", + lifr.lifr_name); + (void) close(fd); + return (0); + } + (void) close(fd); + return (1); +} + +/* + * cif6addr() + */ +/*ARGSUSED*/ +int +cif6addr(u, o, h) + int u; + eui64_t o; + eui64_t h; +{ + if (!IPV6CP_ENABLED) { + return (0); + } + /* + * Do nothing here, as everything has been done in sif6down(). + */ + return (1); +} + +/* + * ether_to_eui64() + * + * Convert 48-bit Ethernet address into 64-bit EUI. Walks the list of valid + * ethernet interfaces, and convert the first found 48-bit MAC address into + * EUI 64. caller also assumes that the system has a properly configured + * Ethernet interface for this function to return non-zero. + */ +int +ether_to_eui64(p_eui64) + eui64_t *p_eui64; +{ + struct ether_addr eth_addr; + + if (p_eui64 == NULL) { + return (0); + } + if (!get_first_hwaddr(eth_addr.ether_addr_octet, + sizeof (eth_addr.ether_addr_octet))) { + return (0); + } + /* + * And convert the EUI-48 into EUI-64, per RFC 2472 [sec 4.1] + */ + p_eui64->e8[0] = (eth_addr.ether_addr_octet[0] & 0xFF) | 0x02; + p_eui64->e8[1] = (eth_addr.ether_addr_octet[1] & 0xFF); + p_eui64->e8[2] = (eth_addr.ether_addr_octet[2] & 0xFF); + p_eui64->e8[3] = 0xFF; + p_eui64->e8[4] = 0xFE; + p_eui64->e8[5] = (eth_addr.ether_addr_octet[3] & 0xFF); + p_eui64->e8[6] = (eth_addr.ether_addr_octet[4] & 0xFF); + p_eui64->e8[7] = (eth_addr.ether_addr_octet[5] & 0xFF); + return (1); +} +#endif /* INET6 */ + +struct bit_ent { + int val; + char *off, *on; +}; + +/* see sbuf[] below if you change this list */ +static struct bit_ent bit_list[] = { + { TIOCM_DTR, "dtr", "DTR" }, + { TIOCM_RTS, "rts", "RTS" }, + { TIOCM_CTS, "cts", "CTS" }, + { TIOCM_CD, "dcd", "DCD" }, + { TIOCM_RI, "ri", "RI" }, + { TIOCM_DSR, "dsr", "DSR" }, +#if 0 + { TIOCM_LE, "disabled", "ENABLED" }, + { TIOCM_ST, NULL, "2nd-XMIT" }, + { TIOCM_SR, NULL, "2nd-RECV" }, +#endif + { 0, NULL, NULL } +}; + +static void +getbits(int fd, char *name, FILE *strptr) +{ + int nmods, i; + struct str_list strlist; + struct bit_ent *be; + int mstate; + char sbuf[50]; /* sum of string lengths in bit_list */ + char *str; + + nmods = ioctl(fd, I_LIST, NULL); + if (nmods < 0) { + error("unable to get module count: %m"); + } else { + strlist.sl_nmods = nmods; + strlist.sl_modlist = malloc(sizeof (struct str_mlist) * nmods); + if (strlist.sl_modlist == NULL) + novm("module list"); + if (ioctl(fd, I_LIST, (caddr_t)&strlist) < 0) { + error("unable to get module names: %m"); + } else { + for (i = 0; i < strlist.sl_nmods; i++) + (void) flprintf(strptr, "%d: %s", i, + strlist.sl_modlist[i].l_name); + free(strlist.sl_modlist); + } + } + if (ioctl(fd, TIOCMGET, &mstate) < 0) { + error("unable to get modem state: %m"); + } else { + sbuf[0] = '\0'; + for (be = bit_list; be->val != 0; be++) { + str = (be->val & mstate) ? be->on : be->off; + if (str != NULL) { + if (sbuf[0] != '\0') + (void) strcat(sbuf, " "); + (void) strcat(sbuf, str); + } + } + (void) flprintf(strptr, "%s: %s\n", name, sbuf); + } +} + +/* + * Print state of serial link. The stream might be linked under the + * /dev/sppp driver. If it is, then it's necessary to unlink it first + * and relink it when done. Otherwise, it's not possible to use + * ioctl() on the stream. + */ +void +sys_print_state(FILE *strptr) +{ + bool was_linked; + + if (pppfd == -1) + return; + if (ttyfd == -1) { + (void) flprintf(strptr, "serial link is not active"); + return; + } + was_linked = fdmuxid != -1; + if (was_linked && ioctl(pppfd, I_UNLINK, fdmuxid) == -1) { + error("I_UNLINK: %m"); + } else { + fdmuxid = -1; + getbits(ttyfd, devnam, strptr); + if (was_linked && + (fdmuxid = ioctl(pppfd, I_LINK, (void *)ttyfd)) == -1) + fatal("I_LINK: %m"); + } +} + +/* + * send ioctl to driver asking it to block packets with network protocol + * proto in the control queue until the queue for proto is plumbed. + */ +void +sys_block_proto(uint16_t proto) +{ + if (proto > 0x7fff) { + warn("cannot block: not a network proto 0x%lx\n", proto); + return; + } + if (strioctl(pppfd, PPPIO_BLOCKNP, &proto, sizeof (proto), 0) < 0) { + warn("PPPIO_BLOCKNP ioctl failed %m"); + } +} +/* + * send ioctl to driver asking it to release packets with network protocol + * proto from control queue to the protocol specific queue. + */ +void +sys_unblock_proto(uint16_t proto) +{ + if (proto > 0x7fff) { + warn("cannot unblock: not a network proto 0x%lx\n", proto); + return; + } + if (strioctl(pppfd, PPPIO_UNBLOCKNP, &proto, sizeof (proto), 0) < 0) { + warn("PPPIO_UNBLOCKNP ioctl failed %m"); + } +} |