* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
-#include <sys/errno.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/sysmacros.h>
+#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <time.h>
#include <memory.h>
+#ifdef HAVE_UTMP_H
#include <utmp.h>
+#endif
#include <mntent.h>
#include <signal.h>
#include <fcntl.h>
#define MAX_ADDR_LEN 7
#endif
-#if __GLIBC__ >= 2
+#if !defined(__GLIBC__) || __GLIBC__ >= 2
#include <asm/types.h> /* glibc 2 conflicts with linux/types.h */
+#include <asm/ioctls.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/route.h>
#include <linux/ppp_defs.h>
#include <linux/if_ppp.h>
+#ifdef INET6
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_addr.h>
+/* glibc versions prior to 2.24 do not define SOL_NETLINK */
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+/* linux kernel versions prior to 4.3 do not define/support NETLINK_CAP_ACK */
+#ifndef NETLINK_CAP_ACK
+#define NETLINK_CAP_ACK 10
+#endif
+#endif
+
#include "pppd.h"
#include "fsm.h"
#include "ipcp.h"
#include <sys/locks.h>
#endif
+#ifndef BOTHER
+#define BOTHER 0010000
+#endif
+struct termios2 {
+ unsigned int c_iflag;
+ unsigned int c_oflag;
+ unsigned int c_cflag;
+ unsigned int c_lflag;
+ unsigned char c_line;
+ unsigned char c_cc[19];
+ unsigned int c_ispeed;
+ unsigned int c_ospeed;
+};
+
#ifdef INET6
#ifndef _LINUX_IN6_H
/*
#endif
#define IN6_LLADDR_FROM_EUI64(sin6, eui64) do { \
- memset(&sin6.s6_addr, 0, sizeof(struct in6_addr)); \
- sin6.s6_addr16[0] = htons(0xfe80); \
- eui64_copy(eui64, sin6.s6_addr32[2]); \
+ memset(&(sin6).s6_addr, 0, sizeof(struct in6_addr)); \
+ (sin6).s6_addr16[0] = htons(0xfe80); \
+ eui64_copy(eui64, (sin6).s6_addr32[2]); \
} while (0)
+static const eui64_t nulleui64;
#endif /* INET6 */
/* We can get an EIO error on an ioctl if the modem has hung up */
static int if_is_up; /* Interface has been marked up */
static int if6_is_up; /* Interface has been marked up for IPv6, to help differentiate */
static int have_default_route; /* Gateway for default route added */
+static int have_default_route6; /* Gateway for default IPv6 route added */
+static struct rtentry old_def_rt; /* Old default route */
+static int default_rt_repl_rest; /* replace and restore old default rt */
static u_int32_t proxy_arp_addr; /* Addr for proxy arp entry added */
static char proxy_arp_dev[16]; /* Device for proxy arp entry */
static u_int32_t our_old_addr; /* for detecting address changes */
static int open_route_table (void);
static int read_route_table (struct rtentry *rt);
static int defaultroute_exists (struct rtentry *rt, int metric);
+static int defaultroute6_exists (struct in6_rtmsg *rt, int metric);
static int get_ether_addr (u_int32_t ipaddr, struct sockaddr *hwaddr,
char *name, int namelen);
static void decode_version (char *buf, int *version, int *mod, int *patch);
*/
if (have_default_route)
cifdefaultroute(0, 0, 0);
+#ifdef INET6
+ if (have_default_route6)
+ cif6defaultroute(0, nulleui64, nulleui64);
+#endif
if (has_proxy_arp)
cifproxyarp(0, proxy_arp_addr);
if (new_style_driver) {
int flags;
+ /* If a ppp_fd is already open, close it first */
+ if (ppp_fd >= 0) {
+ close(ppp_fd);
+ remove_fd(ppp_fd);
+ ppp_fd = -1;
+ }
+
/* Open an instance of /dev/ppp and connect the channel to it */
if (ioctl(fd, PPPIOCGCHAN, &chindex) == -1) {
error("Couldn't get channel number: %m");
* make_ppp_unit - make a new ppp unit for ppp_dev_fd.
* Assumes new_style_driver.
*/
-static int make_ppp_unit()
+static int make_ppp_unit(void)
{
int x, flags;
char t[MAXIFNAMELEN];
memset(&ifr, 0, sizeof(struct ifreq));
slprintf(t, sizeof(t), "%s%d", PPP_DRV_NAME, ifunit);
- strncpy(ifr.ifr_name, t, IF_NAMESIZE);
- strncpy(ifr.ifr_newname, req_ifname, IF_NAMESIZE);
+ strlcpy(ifr.ifr_name, t, IF_NAMESIZE);
+ strlcpy(ifr.ifr_newname, req_ifname, IF_NAMESIZE);
x = ioctl(sock_fd, SIOCSIFNAME, &ifr);
if (x < 0)
error("Couldn't rename interface %s to %s: %m", t, req_ifname);
#ifdef B460800
{ 460800, B460800 },
#endif
+#ifdef B500000
+ { 500000, B500000 },
+#endif
+#ifdef B576000
+ { 576000, B576000 },
+#endif
#ifdef B921600
{ 921600, B921600 },
#endif
if (bps == speedp->speed_int)
return speedp->speed_val;
}
- warn("speed %d not supported", bps);
}
return 0;
}
if (stop_bits >= 2)
tios.c_cflag |= CSTOPB;
- speed = translate_speed(inspeed);
- if (speed) {
- cfsetospeed (&tios, speed);
- cfsetispeed (&tios, speed);
+ if (inspeed) {
+ speed = translate_speed(inspeed);
+ if (speed) {
+ cfsetospeed (&tios, speed);
+ cfsetispeed (&tios, speed);
+ speed = cfgetospeed(&tios);
+ }
+ baud_rate = baud_rate_of(speed);
}
-/*
- * We can't proceed if the serial port speed is B0,
- * since that implies that the serial port is disabled.
- */
else {
speed = cfgetospeed(&tios);
- if (speed == B0)
- fatal("Baud rate for %s is 0; need explicit baud rate", devnam);
+ baud_rate = baud_rate_of(speed);
}
while (tcsetattr(tty_fd, TCSAFLUSH, &tios) < 0 && !ok_error(errno))
if (errno != EINTR)
fatal("tcsetattr: %m (line %d)", __LINE__);
-
- baud_rate = baud_rate_of(speed);
restore_term = 1;
+
+/* Most Linux architectures and drivers support arbitrary baud rate values via BOTHER */
+#ifdef TCGETS2
+ if (!baud_rate) {
+ struct termios2 tios2;
+ if (ioctl(tty_fd, TCGETS2, &tios2) == 0) {
+ if (inspeed) {
+ tios2.c_cflag &= ~CBAUD;
+ tios2.c_cflag |= BOTHER;
+ tios2.c_ispeed = inspeed;
+ tios2.c_ospeed = inspeed;
+#ifdef TCSETS2
+ if (ioctl(tty_fd, TCSETS2, &tios2) == 0)
+ baud_rate = inspeed;
+#endif
+ } else {
+ if ((tios2.c_cflag & CBAUD) == BOTHER && tios2.c_ospeed)
+ baud_rate = tios2.c_ospeed;
+ }
+ }
+ }
+#endif
+
+/*
+ * We can't proceed if the serial port baud rate is unknown,
+ * since that implies that the serial port is disabled.
+ */
+ if (!baud_rate) {
+ if (inspeed)
+ fatal("speed %d not supported", inspeed);
+ else
+ fatal("Baud rate for %s is 0; need explicit baud rate", devnam);
+ }
}
/********************************************************************
* get_idle_time - return how long the link has been idle.
*/
int
-get_idle_time(u, ip)
- int u;
- struct ppp_idle *ip;
+get_idle_time(int u, struct ppp_idle *ip)
{
return ioctl(ppp_dev_fd, PPPIOCGIDLE, ip) >= 0;
}
* get_ppp_stats - return statistics for the link.
*/
int
-get_ppp_stats(u, stats)
- int u;
- struct pppd_stats *stats;
+get_ppp_stats(int u, struct pppd_stats *stats)
{
struct ifpppstatsreq req;
p = NULL;
}
+ SET_SA_FAMILY (rt->rt_dst, AF_INET);
+ SET_SA_FAMILY (rt->rt_gateway, AF_INET);
+
SIN_ADDR(rt->rt_dst) = strtoul(cols[route_dest_col], NULL, 16);
SIN_ADDR(rt->rt_gateway) = strtoul(cols[route_gw_col], NULL, 16);
SIN_ADDR(rt->rt_genmask) = strtoul(cols[route_mask_col], NULL, 16);
/********************************************************************
*
* sifdefaultroute - assign a default route through the address given.
- */
-
-int sifdefaultroute (int unit, u_int32_t ouraddr, u_int32_t gateway)
-{
- struct rtentry rt;
+ *
+ * If the global default_rt_repl_rest flag is set, then this function
+ * already replaced the original system defaultroute with some other
+ * route and it should just replace the current defaultroute with
+ * another one, without saving the current route. Use: demand mode,
+ * when pppd sets first a defaultroute it it's temporary ppp0 addresses
+ * and then changes the temporary addresses to the addresses for the real
+ * ppp connection when it has come up.
+ */
+
+int sifdefaultroute (int unit, u_int32_t ouraddr, u_int32_t gateway, bool replace)
+{
+ struct rtentry rt, tmp_rt;
+ struct rtentry *del_rt = NULL;
+
+ if (default_rt_repl_rest) {
+ /* We have already replaced the original defaultroute, if we
+ * are called again, we will delete the current default route
+ * and set the new default route in this function.
+ * - this is normally only the case the doing demand: */
+ if (defaultroute_exists(&tmp_rt, -1))
+ del_rt = &tmp_rt;
+ } else if (defaultroute_exists(&old_def_rt, -1 ) &&
+ strcmp( old_def_rt.rt_dev, ifname) != 0) {
+ /*
+ * We did not yet replace an existing default route, let's
+ * check if we should save and replace a default route:
+ */
+ u_int32_t old_gateway = SIN_ADDR(old_def_rt.rt_gateway);
- if (defaultroute_exists(&rt, dfl_route_metric) && strcmp(rt.rt_dev, ifname) != 0) {
- if (rt.rt_flags & RTF_GATEWAY)
- error("not replacing existing default route via %I with metric %d",
- SIN_ADDR(rt.rt_gateway), dfl_route_metric);
- else
- error("not replacing existing default route through %s with metric %d",
- rt.rt_dev, dfl_route_metric);
- return 0;
+ if (old_gateway != gateway) {
+ if (!replace) {
+ error("not replacing default route to %s [%I]",
+ old_def_rt.rt_dev, old_gateway);
+ return 0;
+ } else {
+ /* we need to copy rt_dev because we need it permanent too: */
+ char * tmp_dev = malloc(strlen(old_def_rt.rt_dev)+1);
+ strcpy(tmp_dev, old_def_rt.rt_dev);
+ old_def_rt.rt_dev = tmp_dev;
+
+ notice("replacing old default route to %s [%I]",
+ old_def_rt.rt_dev, old_gateway);
+ default_rt_repl_rest = 1;
+ del_rt = &old_def_rt;
+ }
+ }
}
memset (&rt, 0, sizeof (rt));
error("default route ioctl(SIOCADDRT): %m");
return 0;
}
+ if (default_rt_repl_rest && del_rt)
+ if (ioctl(sock_fd, SIOCDELRT, del_rt) < 0) {
+ if ( ! ok_error ( errno ))
+ error("del old default route ioctl(SIOCDELRT): %m(%d)", errno);
+ return 0;
+ }
have_default_route = 1;
return 1;
return 0;
}
}
+ if (default_rt_repl_rest) {
+ notice("restoring old default route to %s [%I]",
+ old_def_rt.rt_dev, SIN_ADDR(old_def_rt.rt_gateway));
+ if (ioctl(sock_fd, SIOCADDRT, &old_def_rt) < 0) {
+ if ( ! ok_error ( errno ))
+ error("restore default route ioctl(SIOCADDRT): %m(%d)", errno);
+ return 0;
+ }
+ default_rt_repl_rest = 0;
+ }
+
+ return 1;
+}
+
+#ifdef INET6
+/*
+ * /proc/net/ipv6_route parsing stuff.
+ */
+static int route_dest_plen_col;
+static int open_route6_table (void);
+static int read_route6_table (struct in6_rtmsg *rt);
+
+/********************************************************************
+ *
+ * open_route6_table - open the interface to the route table
+ */
+static int open_route6_table (void)
+{
+ char *path;
+
+ close_route_table();
+
+ path = path_to_procfs("/net/ipv6_route");
+ route_fd = fopen (path, "r");
+ if (route_fd == NULL) {
+ error("can't open routing table %s: %m", path);
+ return 0;
+ }
+
+ /* default to usual columns */
+ route_dest_col = 0;
+ route_dest_plen_col = 1;
+ route_gw_col = 4;
+ route_metric_col = 5;
+ route_flags_col = 8;
+ route_dev_col = 9;
+ route_num_cols = 10;
+
+ return 1;
+}
+
+/********************************************************************
+ *
+ * read_route6_table - read the next entry from the route table
+ */
+
+static void hex_to_in6_addr(struct in6_addr *addr, const char *s)
+{
+ char hex8[9];
+ unsigned i;
+ uint32_t v;
+
+ hex8[8] = 0;
+ for (i = 0; i < 4; i++) {
+ memcpy(hex8, s + 8*i, 8);
+ v = strtoul(hex8, NULL, 16);
+ addr->s6_addr32[i] = v;
+ }
+}
+
+static int read_route6_table(struct in6_rtmsg *rt)
+{
+ char *cols[ROUTE_MAX_COLS], *p;
+ int col;
+
+ memset (rt, '\0', sizeof (struct in6_rtmsg));
+
+ if (fgets (route_buffer, sizeof (route_buffer), route_fd) == (char *) 0)
+ return 0;
+
+ p = route_buffer;
+ for (col = 0; col < route_num_cols; ++col) {
+ cols[col] = strtok(p, route_delims);
+ if (cols[col] == NULL)
+ return 0; /* didn't get enough columns */
+ p = NULL;
+ }
+
+ hex_to_in6_addr(&rt->rtmsg_dst, cols[route_dest_col]);
+ rt->rtmsg_dst_len = strtoul(cols[route_dest_plen_col], NULL, 16);
+ hex_to_in6_addr(&rt->rtmsg_gateway, cols[route_gw_col]);
+
+ rt->rtmsg_metric = strtoul(cols[route_metric_col], NULL, 16);
+ rt->rtmsg_flags = strtoul(cols[route_flags_col], NULL, 16);
+ rt->rtmsg_ifindex = if_nametoindex(cols[route_dev_col]);
+
+ return 1;
+}
+
+/********************************************************************
+ *
+ * defaultroute6_exists - determine if there is a default route
+ */
+
+static int defaultroute6_exists (struct in6_rtmsg *rt, int metric)
+{
+ int result = 0;
+
+ if (!open_route6_table())
+ return 0;
+
+ while (read_route6_table(rt) != 0) {
+ if ((rt->rtmsg_flags & RTF_UP) == 0)
+ continue;
+
+ if (rt->rtmsg_dst_len != 0)
+ continue;
+ if (rt->rtmsg_dst.s6_addr32[0] == 0L
+ && rt->rtmsg_dst.s6_addr32[1] == 0L
+ && rt->rtmsg_dst.s6_addr32[2] == 0L
+ && rt->rtmsg_dst.s6_addr32[3] == 0L
+ && (metric < 0 || rt->rtmsg_metric == metric)) {
+ result = 1;
+ break;
+ }
+ }
+
+ close_route_table();
+ return result;
+}
+
+/********************************************************************
+ *
+ * sif6defaultroute - assign a default route through the address given.
+ *
+ * If the global default_rt_repl_rest flag is set, then this function
+ * already replaced the original system defaultroute with some other
+ * route and it should just replace the current defaultroute with
+ * another one, without saving the current route. Use: demand mode,
+ * when pppd sets first a defaultroute it it's temporary ppp0 addresses
+ * and then changes the temporary addresses to the addresses for the real
+ * ppp connection when it has come up.
+ */
+
+int sif6defaultroute (int unit, eui64_t ouraddr, eui64_t gateway)
+{
+ struct in6_rtmsg rt;
+ char buf[IF_NAMESIZE];
+
+ if (defaultroute6_exists(&rt, dfl_route_metric) &&
+ rt.rtmsg_ifindex != if_nametoindex(ifname)) {
+ if (rt.rtmsg_flags & RTF_GATEWAY)
+ error("not replacing existing default route via gateway");
+ else
+ error("not replacing existing default route through %s",
+ if_indextoname(rt.rtmsg_ifindex, buf));
+ return 0;
+ }
+
+ memset (&rt, 0, sizeof (rt));
+
+ rt.rtmsg_ifindex = if_nametoindex(ifname);
+ rt.rtmsg_metric = dfl_route_metric + 1; /* +1 for binary compatibility */
+ rt.rtmsg_dst_len = 0;
+
+ rt.rtmsg_flags = RTF_UP;
+ if (ioctl(sock6_fd, SIOCADDRT, &rt) < 0) {
+ if ( ! ok_error ( errno ))
+ error("default route ioctl(SIOCADDRT): %m");
+ return 0;
+ }
+
+ have_default_route6 = 1;
+ return 1;
+}
+
+/********************************************************************
+ *
+ * cif6defaultroute - delete a default route through the address given.
+ */
+
+int cif6defaultroute (int unit, eui64_t ouraddr, eui64_t gateway)
+{
+ struct in6_rtmsg rt;
+
+ have_default_route6 = 0;
+
+ memset (&rt, '\0', sizeof (rt));
+
+ rt.rtmsg_ifindex = if_nametoindex(ifname);
+ rt.rtmsg_metric = dfl_route_metric + 1; /* +1 for binary compatibility */
+ rt.rtmsg_dst_len = 0;
+
+ rt.rtmsg_flags = RTF_UP;
+ if (ioctl(sock6_fd, SIOCDELRT, &rt) < 0 && errno != ESRCH) {
+ if (still_ppp()) {
+ if ( ! ok_error ( errno ))
+ error("default route ioctl(SIOCDELRT): %m");
+ return 0;
+ }
+ }
return 1;
}
+#endif /* INET6 */
/********************************************************************
*
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0)
- return 0;
+ return -1;
memset(&ifreq.ifr_hwaddr, 0, sizeof(struct sockaddr));
strlcpy(ifreq.ifr_name, name, sizeof(ifreq.ifr_name));
ret = ioctl(sock_fd, SIOCGIFHWADDR, &ifreq);
}
/*
- * get_first_ethernet - return the name of the first ethernet-style
- * interface on this system.
+ * get_first_ether_hwaddr - get the hardware address for the first
+ * ethernet-style interface on this system.
*/
-char *
-get_first_ethernet()
+int
+get_first_ether_hwaddr(u_char *addr)
{
- return "eth0";
+ struct if_nameindex *if_ni, *i;
+ struct ifreq ifreq;
+ int ret, sock_fd;
+
+ sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock_fd < 0)
+ return -1;
+
+ if_ni = if_nameindex();
+ if (!if_ni) {
+ close(sock_fd);
+ return -1;
+ }
+
+ ret = -1;
+
+ for (i = if_ni; !(i->if_index == 0 && i->if_name == NULL); i++) {
+ memset(&ifreq.ifr_hwaddr, 0, sizeof(struct sockaddr));
+ strlcpy(ifreq.ifr_name, i->if_name, sizeof(ifreq.ifr_name));
+ ret = ioctl(sock_fd, SIOCGIFHWADDR, &ifreq);
+ if (ret >= 0 && ifreq.ifr_hwaddr.sa_family == ARPHRD_ETHER) {
+ memcpy(addr, ifreq.ifr_hwaddr.sa_data, 6);
+ break;
+ }
+ ret = -1;
+ }
+
+ if_freenameindex(if_ni);
+ close(sock_fd);
+
+ return ret;
}
/********************************************************************
}
}
- close (s);
if (!ok) {
slprintf(route_buffer, sizeof(route_buffer),
"Sorry - PPP driver version %d.%d.%d is out of date\n",
}
}
}
+ close(s);
return ok;
}
}
#ifdef INET6
+/********************************************************************
+ *
+ * sif6addr_rtnetlink - Config the interface with both IPv6 link-local addresses via rtnetlink
+ */
+static int sif6addr_rtnetlink(unsigned int iface, eui64_t our_eui64, eui64_t his_eui64)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct ifaddrmsg ifa;
+ struct {
+ struct rtattr rta;
+ struct in6_addr addr;
+ } addrs[2];
+ } nlreq;
+ struct {
+ struct nlmsghdr nlh;
+ struct nlmsgerr nlerr;
+ } nlresp;
+ struct sockaddr_nl nladdr;
+ struct iovec iov;
+ struct msghdr msg;
+ ssize_t nlresplen;
+ int one;
+ int fd;
+
+ fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (fd < 0) {
+ error("sif6addr_rtnetlink: socket(NETLINK_ROUTE): %m (line %d)", __LINE__);
+ return 0;
+ }
+
+ /*
+ * Tell kernel to not send to us payload of acknowledgment error message.
+ * NETLINK_CAP_ACK option is supported since Linux kernel version 4.3 and
+ * older kernel versions always send full payload in acknowledgment netlink
+ * message. We ignore payload of this message as we need only error code,
+ * to check if our set remote peer address request succeeded or failed.
+ * So ignore return value from the following setsockopt() call as setting
+ * option NETLINK_CAP_ACK means for us just a kernel hint / optimization.
+ */
+ one = 1;
+ setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &one, sizeof(one));
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ if (bind(fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) {
+ error("sif6addr_rtnetlink: bind(AF_NETLINK): %m (line %d)", __LINE__);
+ close(fd);
+ return 0;
+ }
+
+ memset(&nlreq, 0, sizeof(nlreq));
+ nlreq.nlh.nlmsg_len = sizeof(nlreq);
+ nlreq.nlh.nlmsg_type = RTM_NEWADDR;
+ nlreq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;
+ nlreq.ifa.ifa_family = AF_INET6;
+ nlreq.ifa.ifa_prefixlen = 128;
+ nlreq.ifa.ifa_flags = IFA_F_NODAD | IFA_F_PERMANENT;
+ nlreq.ifa.ifa_scope = RT_SCOPE_LINK;
+ nlreq.ifa.ifa_index = iface;
+ nlreq.addrs[0].rta.rta_len = sizeof(nlreq.addrs[0]);
+ nlreq.addrs[0].rta.rta_type = IFA_LOCAL;
+ IN6_LLADDR_FROM_EUI64(nlreq.addrs[0].addr, our_eui64);
+ nlreq.addrs[1].rta.rta_len = sizeof(nlreq.addrs[1]);
+ nlreq.addrs[1].rta.rta_type = IFA_ADDRESS;
+ IN6_LLADDR_FROM_EUI64(nlreq.addrs[1].addr, his_eui64);
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = &nlreq;
+ iov.iov_len = sizeof(nlreq);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = &nladdr;
+ msg.msg_namelen = sizeof(nladdr);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ if (sendmsg(fd, &msg, 0) < 0) {
+ error("sif6addr_rtnetlink: sendmsg(RTM_NEWADDR/NLM_F_CREATE): %m (line %d)", __LINE__);
+ close(fd);
+ return 0;
+ }
+
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = &nlresp;
+ iov.iov_len = sizeof(nlresp);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = &nladdr;
+ msg.msg_namelen = sizeof(nladdr);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ nlresplen = recvmsg(fd, &msg, 0);
+
+ if (nlresplen < 0) {
+ error("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): %m (line %d)", __LINE__);
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+
+ if (nladdr.nl_family != AF_NETLINK) {
+ error("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): Not a netlink packet (line %d)", __LINE__);
+ return 0;
+ }
+
+ if ((size_t)nlresplen != sizeof(nlresp) || nlresp.nlh.nlmsg_len < sizeof(nlresp)) {
+ error("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): Acknowledgment netlink packet too short (line %d)", __LINE__);
+ return 0;
+ }
+
+ /* acknowledgment packet for NLM_F_ACK is NLMSG_ERROR */
+ if (nlresp.nlh.nlmsg_type != NLMSG_ERROR) {
+ error("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): Not an acknowledgment netlink packet (line %d)", __LINE__);
+ return 0;
+ }
+
+ /* error == 0 indicates success, negative value is errno code */
+ if (nlresp.nlerr.error != 0) {
+ /*
+ * Linux kernel versions prior 3.11 do not support setting IPv6 peer
+ * addresses and error response is expected. On older kernel versions
+ * do not show this error message. On error pppd tries to fallback to
+ * the old IOCTL method.
+ */
+ if (kernel_version >= KVERSION(3,11,0))
+ error("sif6addr_rtnetlink: %s (line %d)", strerror(-nlresp.nlerr.error), __LINE__);
+ return 0;
+ }
+
+ return 1;
+}
+
/********************************************************************
*
* sif6addr - Config the interface with an IPv6 link-local address
struct in6_ifreq ifr6;
struct ifreq ifr;
struct in6_rtmsg rt6;
+ int ret;
if (sock6_fd < 0) {
errno = -sock6_fd;
return 0;
}
- /* Local interface */
- memset(&ifr6, 0, sizeof(ifr6));
- IN6_LLADDR_FROM_EUI64(ifr6.ifr6_addr, our_eui64);
- ifr6.ifr6_ifindex = ifr.ifr_ifindex;
- ifr6.ifr6_prefixlen = 10;
-
- if (ioctl(sock6_fd, SIOCSIFADDR, &ifr6) < 0) {
- error("sif6addr: ioctl(SIOCSIFADDR): %m (line %d)", __LINE__);
- return 0;
+ if (kernel_version >= KVERSION(2,1,16)) {
+ /* Set both local address and remote peer address (with route for it) via rtnetlink */
+ ret = sif6addr_rtnetlink(ifr.ifr_ifindex, our_eui64, his_eui64);
+ } else {
+ ret = 0;
}
- /* Route to remote host */
- memset(&rt6, 0, sizeof(rt6));
- IN6_LLADDR_FROM_EUI64(rt6.rtmsg_dst, his_eui64);
- rt6.rtmsg_flags = RTF_UP;
- rt6.rtmsg_dst_len = 10;
- rt6.rtmsg_ifindex = ifr.ifr_ifindex;
- rt6.rtmsg_metric = 1;
-
- if (ioctl(sock6_fd, SIOCADDRT, &rt6) < 0) {
- error("sif6addr: ioctl(SIOCADDRT): %m (line %d)", __LINE__);
- return 0;
+ /*
+ * Linux kernel versions prior 3.11 do not support setting IPv6 peer address
+ * via rtnetlink. So if sif6addr_rtnetlink() fails then try old IOCTL method.
+ */
+ if (!ret) {
+ /* Local interface */
+ memset(&ifr6, 0, sizeof(ifr6));
+ IN6_LLADDR_FROM_EUI64(ifr6.ifr6_addr, our_eui64);
+ ifr6.ifr6_ifindex = ifr.ifr_ifindex;
+ ifr6.ifr6_prefixlen = 128;
+
+ if (ioctl(sock6_fd, SIOCSIFADDR, &ifr6) < 0) {
+ error("sif6addr: ioctl(SIOCSIFADDR): %m (line %d)", __LINE__);
+ return 0;
+ }
+
+ /*
+ * Linux kernel does not provide AF_INET6 ioctl SIOCSIFDSTADDR for
+ * setting remote peer host address, so set only route to remote host.
+ */
+
+ /* Route to remote host */
+ memset(&rt6, 0, sizeof(rt6));
+ IN6_LLADDR_FROM_EUI64(rt6.rtmsg_dst, his_eui64);
+ rt6.rtmsg_flags = RTF_UP;
+ rt6.rtmsg_dst_len = 128;
+ rt6.rtmsg_ifindex = ifr.ifr_ifindex;
+ rt6.rtmsg_metric = 1;
+
+ if (ioctl(sock6_fd, SIOCADDRT, &rt6) < 0) {
+ error("sif6addr: ioctl(SIOCADDRT): %m (line %d)", __LINE__);
+ return 0;
+ }
}
return 1;
memset(&ifr6, 0, sizeof(ifr6));
IN6_LLADDR_FROM_EUI64(ifr6.ifr6_addr, our_eui64);
ifr6.ifr6_ifindex = ifr.ifr_ifindex;
- ifr6.ifr6_prefixlen = 10;
+ ifr6.ifr6_prefixlen = 128;
if (ioctl(sock6_fd, SIOCDIFADDR, &ifr6) < 0) {
if (errno != EADDRNOTAVAIL) {
* to the uid given. Assumes slave_name points to >= 16 bytes of space.
*/
int
-get_pty(master_fdp, slave_fdp, slave_name, uid)
- int *master_fdp;
- int *slave_fdp;
- char *slave_name;
- int uid;
+get_pty(int *master_fdp, int *slave_fdp, char *slave_name, int uid)
{
int i, mfd, sfd = -1;
char pty_name[16];
warn("Couldn't unlock pty slave %s: %m", pty_name);
#endif
if ((sfd = open(pty_name, O_RDWR | O_NOCTTY)) < 0)
+ {
warn("Couldn't open pty slave %s: %m", pty_name);
+ close(mfd);
+ }
}
}
#endif /* TIOCGPTN */
*/
int
-sifnpmode(u, proto, mode)
- int u;
- int proto;
- enum NPmode mode;
+sifnpmode(int u, int proto, enum NPmode mode)
{
struct npioctl npi;
* Use the hostname as part of the random number seed.
*/
int
-get_host_seed()
+get_host_seed(void)
{
int h;
char *p = hostname;
return 1;
}
-#ifdef INET6
-/*
- * ether_to_eui64 - Convert 48-bit Ethernet address into 64-bit EUI
+/********************************************************************
*
- * convert the 48-bit MAC address of eth0 into EUI 64. caller also assumes
- * that the system has a properly configured Ethernet interface for this
- * function to return non-zero.
+ * get_time - Get current time, monotonic if possible.
*/
int
-ether_to_eui64(eui64_t *p_eui64)
+get_time(struct timeval *tv)
{
- struct ifreq ifr;
- int skfd;
- const unsigned char *ptr;
+/* Old glibc (< 2.3.4) does define CLOCK_MONOTONIC, but kernel may have it.
+ * Runtime checking makes it safe. */
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC 1
+#endif
+ static int monotonic = -1;
+ struct timespec ts;
+ int ret;
- skfd = socket(PF_INET6, SOCK_DGRAM, 0);
- if(skfd == -1)
- {
- warn("could not open IPv6 socket");
- return 0;
- }
+ if (monotonic) {
+ ret = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (ret == 0) {
+ monotonic = 1;
+ if (tv) {
+ tv->tv_sec = ts.tv_sec;
+ tv->tv_usec = ts.tv_nsec / 1000;
+ }
+ return ret;
+ } else if (monotonic > 0)
+ return ret;
- strcpy(ifr.ifr_name, "eth0");
- if(ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0)
- {
- close(skfd);
- warn("could not obtain hardware address for eth0");
- return 0;
+ monotonic = 0;
+ warn("Couldn't use monotonic clock source: %m");
}
- close(skfd);
-
- /*
- * And convert the EUI-48 into EUI-64, per RFC 2472 [sec 4.1]
- */
- ptr = (unsigned char *) ifr.ifr_hwaddr.sa_data;
- p_eui64->e8[0] = ptr[0] | 0x02;
- p_eui64->e8[1] = ptr[1];
- p_eui64->e8[2] = ptr[2];
- p_eui64->e8[3] = 0xFF;
- p_eui64->e8[4] = 0xFE;
- p_eui64->e8[5] = ptr[3];
- p_eui64->e8[6] = ptr[4];
- p_eui64->e8[7] = ptr[5];
- return 1;
+ return gettimeofday(tv, NULL);
}
-#endif