#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/sockio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <net/if_dl.h>
#include <net/if_tun.h>
#include <net/if_types.h>
#include <net/if.h>
#include <net/pipex.h>
#include <fcntl.h>
#include <syslog.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include <time.h>
#include <event.h>
#include "radish.h"
#include "npppd_defs.h"
#include "npppd_local.h"
#include "npppd_subr.h"
#include "debugutil.h"
#include "npppd_iface.h"
#ifdef USE_NPPPD_PIPEX
#include <net/if.h>
#if defined(__NetBSD__)
#include <net/if_ether.h>
#else
#include <netinet/if_ether.h>
#endif
#include <net/pipex.h>
#endif
#ifdef NPPPD_IFACE_DEBUG
#define NPPPD_IFACE_DBG(x) npppd_iface_log x
#define NPPPD_IFACE_ASSERT(cond) \
if (!(cond)) { \
fprintf(stderr, \
"\nASSERT(" #cond ") failed on %s() at %s:%d.\n"\
, __func__, __FILE__, __LINE__); \
abort(); \
}
#else
#define NPPPD_IFACE_ASSERT(cond)
#define NPPPD_IFACE_DBG(x)
#endif
static void npppd_iface_network_input_ipv4(npppd_iface *, struct pppx_hdr *,
u_char *, int);
static void npppd_iface_network_input(npppd_iface *, u_char *, int);
static int npppd_iface_setup_ip(npppd_iface *);
static void npppd_iface_io_event_handler (int, short, void *);
static int npppd_iface_log (npppd_iface *, int, const char *, ...)
__printflike(3,4);
void
npppd_iface_init(npppd *npppd, npppd_iface *_this, struct iface *iface)
{
NPPPD_IFACE_ASSERT(_this != NULL);
memset(_this, 0, sizeof(npppd_iface));
_this->npppd = npppd;
strlcpy(_this->ifname, iface->name, sizeof(_this->ifname));
_this->using_pppx = iface->is_pppx;
_this->set_ip4addr = 1;
_this->ip4addr = iface->ip4addr;
_this->ipcpconf = iface->ipcpconf;
_this->devf = -1;
_this->initialized = 1;
}
static int
npppd_iface_setup_ip(npppd_iface *_this)
{
int sock, if_flags, changed;
struct in_addr gw, assigned;
struct sockaddr_in *sin0;
struct ifreq ifr;
struct ifaliasreq ifra;
npppd_ppp *ppp;
NPPPD_IFACE_ASSERT(_this != NULL);
sock = -1;
changed = 0;
memset(&ifr, 0, sizeof(ifr));
assigned.s_addr = INADDR_NONE;
memset(&ifr, 0, sizeof(ifr));
memset(&ifra, 0, sizeof(ifra));
strlcpy(ifr.ifr_name, _this->ifname, sizeof(ifr.ifr_name));
strlcpy(ifra.ifra_name, _this->ifname, sizeof(ifra.ifra_name));
sin0 = (struct sockaddr_in *)&ifr.ifr_addr;
if (priv_get_if_addr(_this->ifname, &assigned) != 0) {
if (errno != EADDRNOTAVAIL) {
npppd_iface_log(_this, LOG_ERR,
"get ip address failed: %m");
goto fail;
}
assigned.s_addr = 0;
}
if (assigned.s_addr != _this->ip4addr.s_addr)
changed = 1;
if (priv_get_if_flags(_this->ifname, &if_flags) != 0) {
npppd_iface_log(_this, LOG_ERR,
"ioctl(,SIOCGIFFLAGS) failed: %m");
goto fail;
}
if_flags = ifr.ifr_flags;
if (_this->set_ip4addr != 0 && changed) {
do {
struct in_addr dummy;
if (priv_delete_if_addr(_this->ifname) != 0) {
if (errno == EADDRNOTAVAIL)
break;
npppd_iface_log(_this, LOG_ERR,
"delete ipaddress %s failed: %m",
_this->ifname);
goto fail;
}
if (priv_get_if_addr(_this->ifname, &dummy) != 0) {
if (errno == EADDRNOTAVAIL)
break;
npppd_iface_log(_this, LOG_ERR,
"cannot get ipaddress %s failed: %m",
_this->ifname);
goto fail;
}
} while (1);
if (priv_set_if_flags(_this->ifname,
if_flags & ~(IFF_UP | IFF_BROADCAST)) != 0) {
npppd_iface_log(_this, LOG_ERR,
"disabling %s failed: %m", _this->ifname);
goto fail;
}
if (priv_set_if_addr(_this->ifname, &_this->ip4addr) != 0 &&
errno != EEXIST) {
npppd_iface_log(_this, LOG_ERR,
"Cannot assign tun device ip address: %m");
goto fail;
}
if (assigned.s_addr != 0) {
gw.s_addr = htonl(INADDR_LOOPBACK);
in_host_route_delete(&assigned, &gw);
}
assigned.s_addr = _this->ip4addr.s_addr;
}
_this->ip4addr.s_addr = assigned.s_addr;
if (npppd_iface_ip_is_ready(_this)) {
if (changed) {
ppp = npppd_get_ppp_by_ip(_this->npppd, _this->ip4addr);
if (ppp != NULL) {
npppd_iface_log(_this, LOG_ERR,
"Assigning %s, but ppp=%d is using "
"the address. Requested the ppp to stop",
inet_ntoa(_this->ip4addr), ppp->id);
ppp_stop(ppp, "Administrative reason");
}
}
if (priv_set_if_flags(_this->ifname,
if_flags | IFF_UP | IFF_MULTICAST) != 0) {
npppd_iface_log(_this, LOG_ERR,
"enabling %s failed: %m", _this->ifname);
goto fail;
}
gw.s_addr = htonl(INADDR_LOOPBACK);
in_host_route_add(&_this->ip4addr, &gw, LOOPBACK_IFNAME, 0);
}
close(sock); sock = -1;
return 0;
fail:
if (sock >= 0)
close(sock);
return 1;
}
int
npppd_iface_reinit(npppd_iface *_this, struct iface *iface)
{
int rval;
struct in_addr backup;
char buf0[128], buf1[128];
_this->ipcpconf = iface->ipcpconf;
backup = _this->ip4addr;
_this->ip4addr = iface->ip4addr;
if (_this->using_pppx)
return 0;
if ((rval = npppd_iface_setup_ip(_this)) != 0)
return rval;
if (backup.s_addr != _this->ip4addr.s_addr) {
npppd_iface_log(_this, LOG_INFO, "Reinited ip4addr %s=>%s",
(backup.s_addr != INADDR_ANY)
? inet_ntop(AF_INET, &backup, buf0, sizeof(buf0))
: "(not assigned)",
(_this->ip4addr.s_addr != INADDR_ANY)
? inet_ntop(AF_INET, &_this->ip4addr, buf1,
sizeof(buf1))
: "(not assigned)");
}
return 0;
}
int
npppd_iface_start(npppd_iface *_this)
{
char buf[PATH_MAX];
NPPPD_IFACE_ASSERT(_this != NULL);
snprintf(buf, sizeof(buf), "/dev/%s", _this->ifname);
if ((_this->devf = priv_open(buf, O_RDWR | O_NONBLOCK)) < 0) {
npppd_iface_log(_this, LOG_ERR, "open(%s) failed: %m", buf);
goto fail;
}
event_set(&_this->ev, _this->devf, EV_READ | EV_PERSIST,
npppd_iface_io_event_handler, _this);
event_add(&_this->ev, NULL);
if (_this->using_pppx == 0) {
if (npppd_iface_setup_ip(_this) != 0)
goto fail;
}
#ifndef USE_NPPPD_PIPEX
if (_this->using_pppx) {
npppd_iface_log(_this, LOG_ERR,
"pipex is required when using pppx interface");
goto fail;
}
#endif
if (_this->using_pppx) {
npppd_iface_log(_this, LOG_INFO, "Started pppx");
} else {
npppd_iface_log(_this, LOG_INFO, "Started ip4addr=%s",
(npppd_iface_ip_is_ready(_this))?
inet_ntop(AF_INET, &_this->ip4addr, buf,
sizeof(buf)) : "(not assigned)");
}
_this->started = 1;
return 0;
fail:
if (_this->devf >= 0) {
event_del(&_this->ev);
close(_this->devf);
}
_this->devf = -1;
return -1;
}
void
npppd_iface_stop(npppd_iface *_this)
{
struct in_addr gw;
NPPPD_IFACE_ASSERT(_this != NULL);
if (_this->using_pppx == 0) {
priv_delete_if_addr(_this->ifname);
gw.s_addr = htonl(INADDR_LOOPBACK);
in_host_route_delete(&_this->ip4addr, &gw);
}
if (_this->devf >= 0) {
event_del(&_this->ev);
close(_this->devf);
npppd_iface_log(_this, LOG_INFO, "Stopped");
}
_this->devf = -1;
_this->started = 0;
event_del(&_this->ev);
}
void
npppd_iface_fini(npppd_iface *_this)
{
NPPPD_IFACE_ASSERT(_this != NULL);
_this->initialized = 0;
}
static void
npppd_iface_io_event_handler(int fd, short evtype, void *data)
{
int sz;
u_char buffer[8192];
npppd_iface *_this;
NPPPD_IFACE_ASSERT((evtype & EV_READ) != 0);
_this = data;
NPPPD_IFACE_ASSERT(_this->devf >= 0);
do {
sz = read(_this->devf, buffer, sizeof(buffer));
if (sz <= 0) {
if (sz == 0)
npppd_iface_log(_this, LOG_ERR,
"file is closed");
else if (errno == EAGAIN)
break;
else
npppd_iface_log(_this, LOG_ERR,
"read failed: %m");
npppd_iface_stop(_this);
return;
}
npppd_iface_network_input(_this, buffer, sz);
} while (1 );
return;
}
struct npppd_iface_network_input_arg{
npppd_iface *_this;
u_char *pktp;
int lpktp;
};
static int
npppd_iface_network_input_delegate(struct radish *radish, void *args0)
{
npppd_ppp *ppp;
struct sockaddr_npppd *snp;
struct npppd_iface_network_input_arg *args;
snp = radish->rd_rtent;
if (snp->snp_type == SNP_PPP) {
args = args0;
ppp = snp->snp_data_ptr;
if (ppp_iface(ppp) != args->_this)
return 0;
#ifdef USE_NPPPD_MPPE
if (MPPE_SEND_READY(ppp)) {
mppe_pkt_output(&ppp->mppe, PPP_PROTO_IP, args->pktp,
args->lpktp);
} else if (MPPE_IS_REQUIRED(ppp)) {
return 0;
}
#endif
ppp_output(ppp, PPP_PROTO_IP, 0, 0, args->pktp, args->lpktp);
}
return 0;
}
static void
npppd_iface_network_input_ipv4(npppd_iface *_this, struct pppx_hdr *pppx,
u_char *pktp, int lpktp)
{
struct ip *iphdr;
npppd *_npppd;
npppd_ppp *ppp;
struct npppd_iface_network_input_arg input_arg;
NPPPD_IFACE_ASSERT(_this != NULL);
NPPPD_IFACE_ASSERT(pktp != NULL);
iphdr = (struct ip *)pktp;
_npppd = _this->npppd;
if (lpktp < sizeof(iphdr)) {
npppd_iface_log(_this, LOG_ERR, "Received short packet.");
return;
}
if (_this->using_pppx)
ppp = npppd_get_ppp_by_id(_npppd, pppx->pppx_id);
else {
if (IN_MULTICAST(ntohl(iphdr->ip_dst.s_addr))) {
NPPPD_IFACE_ASSERT(
((npppd *)(_this->npppd))->rd != NULL);
input_arg._this = _this;
input_arg.pktp = pktp;
input_arg.lpktp = lpktp;
rd_walktree(((npppd *)(_this->npppd))->rd,
npppd_iface_network_input_delegate, &input_arg);
return;
}
ppp = npppd_get_ppp_by_ip(_npppd, iphdr->ip_dst);
}
if (ppp == NULL) {
#ifdef NPPPD_DEBUG
log_printf(LOG_INFO, "%s received a packet to unknown "
"%s.", _this->ifname, inet_ntoa(iphdr->ip_dst));
#endif
return;
}
#ifndef NO_ADJUST_MSS
if (ppp->adjust_mss) {
adjust_tcp_mss(pktp, lpktp, MRU_IPMTU(ppp->peer_mru));
}
#endif
if (ppp->timeout_sec > 0 && !ip_is_idle_packet(iphdr, lpktp))
ppp_reset_idle_timeout(ppp);
#ifdef USE_NPPPD_MPPE
if (MPPE_SEND_READY(ppp)) {
mppe_pkt_output(&ppp->mppe, PPP_PROTO_IP, pktp, lpktp);
return;
} else if (MPPE_IS_REQUIRED(ppp)) {
ppp_log(ppp, LOG_WARNING, "A packet received from network, "
"but MPPE is not started.");
return;
}
#endif
ppp_output(ppp, PPP_PROTO_IP, 0, 0, pktp, lpktp);
}
static void
npppd_iface_network_input(npppd_iface *_this, u_char *pktp, int lpktp)
{
uint32_t af;
struct pppx_hdr *pppx = NULL;
if (_this->using_pppx) {
if (lpktp < sizeof(struct pppx_hdr)) {
npppd_iface_log(_this, LOG_ERR,
"Received short packet.");
return;
}
pppx = (struct pppx_hdr *)pktp;
pktp += sizeof(struct pppx_hdr);
lpktp -= sizeof(struct pppx_hdr);
}
if (lpktp < sizeof(uint32_t)) {
npppd_iface_log(_this, LOG_ERR, "Received short packet.");
return;
}
GETLONG(af, pktp);
lpktp -= sizeof(uint32_t);
switch (af) {
case AF_INET:
npppd_iface_network_input_ipv4(_this, pppx, pktp, lpktp);
break;
default:
NPPPD_IFACE_ASSERT(0);
break;
}
}
void
npppd_iface_write(npppd_iface *_this, npppd_ppp *ppp, int proto, u_char *pktp,
int lpktp)
{
int niov = 0, tlen;
uint32_t th;
struct iovec iov[3];
struct pppx_hdr pppx;
NPPPD_IFACE_ASSERT(_this != NULL);
NPPPD_IFACE_ASSERT(_this->devf >= 0);
tlen = 0;
th = htonl(proto);
if (_this->using_pppx) {
pppx.pppx_proto = npppd_pipex_proto(ppp->tunnel_type);
pppx.pppx_id = ppp->tunnel_session_id;
iov[niov].iov_base = &pppx;
iov[niov++].iov_len = sizeof(pppx);
tlen += sizeof(pppx);
}
iov[niov].iov_base = &th;
iov[niov++].iov_len = sizeof(th);
tlen += sizeof(th);
iov[niov].iov_base = pktp;
iov[niov++].iov_len = lpktp;
tlen += lpktp;
if (writev(_this->devf, iov, niov) != tlen)
npppd_iface_log(_this, LOG_ERR, "write failed: %m");
}
static int
npppd_iface_log(npppd_iface *_this, int prio, const char *fmt, ...)
{
int status;
char logbuf[BUFSIZ];
va_list ap;
NPPPD_IFACE_ASSERT(_this != NULL);
va_start(ap, fmt);
snprintf(logbuf, sizeof(logbuf), "%s %s", _this->ifname, fmt);
status = vlog_printf(prio, logbuf, ap);
va_end(ap);
return status;
}