#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <net/route.h>
#include <net/if_dl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <syslog.h>
#include <stddef.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <resolv.h>
#include "debugutil.h"
#include "addr_range.h"
#include "npppd_defs.h"
#include "npppd_subr.h"
#include "privsep.h"
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
static u_int16_t route_seq = 0;
static int in_route0(int, struct in_addr *, struct in_addr *, struct in_addr *, int, const char *, uint32_t);
#define ROUNDUP(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
static const char *
skip_space(const char *s)
{
const char *r;
for (r = s; *r != '\0' && isspace((unsigned char)*r); r++)
;
return r;
}
int
load_resolv_conf(struct in_addr *pri, struct in_addr *sec)
{
FILE *filep;
int i;
struct in_addr *addr;
char *ap, *line, buf[BUFSIZ];
pri->s_addr = INADDR_NONE;
sec->s_addr = INADDR_NONE;
filep = NULL;
if ((filep = priv_fopen(_PATH_RESCONF)) == NULL)
return 1;
i = 0;
while (fgets(buf, sizeof(buf), filep) != NULL) {
line = (char *)skip_space(buf);
if (strncmp(line, "nameserver", 10) != 0)
continue;
line += 10;
if (!isspace((unsigned char)*line))
continue;
while ((ap = strsep(&line, " \t\r\n")) != NULL) {
if (*ap == '\0')
continue;
if (i == 0)
addr = pri;
else
addr = sec;
if (inet_pton(AF_INET, ap, addr) != 1) {
continue;
}
addr->s_addr = addr->s_addr;
if (++i >= 2)
goto end_loop;
}
}
end_loop:
if (filep != NULL)
fclose(filep);
return 0;
}
static int
in_route0(int type, struct in_addr *dest, struct in_addr *mask,
struct in_addr *gate, int mtu, const char *ifname, uint32_t rtm_flags)
{
struct rt_msghdr *rtm;
struct sockaddr_in sdest, smask, sgate;
struct sockaddr_dl *sdl;
char dl_buf[512];
char *cp, buf[sizeof(*rtm) + sizeof(struct sockaddr_in) * 3 +
sizeof(dl_buf) + 128];
const char *strtype;
int rval, flags, sock;
sock = -1;
ASSERT(type == RTM_ADD || type == RTM_DELETE);
if(type == RTM_ADD)
strtype = "RTM_ADD";
else
strtype = "RTM_DELETE";
memset(buf, 0, sizeof(buf));
memset(&sdest, 0, sizeof(sdest));
memset(&smask, 0, sizeof(smask));
memset(&sgate, 0, sizeof(sgate));
memset(&dl_buf, 0, sizeof(dl_buf));
sdl = (struct sockaddr_dl *)dl_buf;
sdest.sin_addr = *dest;
if (mask != NULL)
smask.sin_addr = *mask;
if (gate != NULL)
sgate.sin_addr = *gate;
sdest.sin_family = smask.sin_family = sgate.sin_family = AF_INET;
sdest.sin_len = smask.sin_len = sgate.sin_len = sizeof(sgate);
rtm = (struct rt_msghdr *)buf;
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = type;
rtm->rtm_flags = rtm_flags;
if (gate != NULL)
rtm->rtm_flags |= RTF_GATEWAY;
if (mask == NULL)
rtm->rtm_flags |= RTF_HOST;
if (type == RTM_ADD && mtu > 0) {
rtm->rtm_inits = RTV_MTU;
rtm->rtm_rmx.rmx_mtu = mtu;
}
if (type == RTM_ADD)
rtm->rtm_flags |= RTF_UP;
rtm->rtm_addrs = RTA_DST;
if (gate != NULL)
rtm->rtm_addrs |= RTA_GATEWAY;
if (mask != NULL)
rtm->rtm_addrs |= RTA_NETMASK;
#ifdef RTA_IFP
if (ifname != NULL)
rtm->rtm_addrs |= RTA_IFP;
#endif
rtm->rtm_pid = getpid();
route_seq = ((route_seq + 1)&0x0000ffff);
rtm->rtm_seq = route_seq;
cp = (char *)rtm;
cp += ROUNDUP(sizeof(*rtm));
memcpy(cp, &sdest, sdest.sin_len);
cp += ROUNDUP(sdest.sin_len);
if (gate != NULL) {
memcpy(cp, &sgate, sgate.sin_len);
cp += ROUNDUP(sgate.sin_len);
}
if (mask != NULL) {
memcpy(cp, &smask, smask.sin_len);
cp += ROUNDUP(smask.sin_len);
}
#ifdef RTA_IFP
if (ifname != NULL) {
strlcpy(sdl->sdl_data, ifname, IFNAMSIZ);
sdl->sdl_family = AF_LINK;
sdl->sdl_len = offsetof(struct sockaddr_dl, sdl_data) +IFNAMSIZ;
sdl->sdl_index = if_nametoindex(ifname);
memcpy(cp, sdl, sdl->sdl_len);
cp += ROUNDUP(sdl->sdl_len);
}
#endif
rtm->rtm_msglen = cp - buf;
if ((sock = priv_socket(AF_ROUTE, SOCK_RAW, AF_UNSPEC)) < 0) {
log_printf(LOG_ERR, "socket() failed in %s() on %s : %m",
__func__, strtype);
goto fail;
}
if ((flags = fcntl(sock, F_GETFL)) < 0) {
log_printf(LOG_ERR, "fcntl(,F_GETFL) failed on %s : %m",
__func__);
goto fail;
}
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
log_printf(LOG_ERR, "fcntl(,F_SETFL) failed on %s : %m",
__func__);
goto fail;
}
if ((rval = priv_send(sock, buf, rtm->rtm_msglen, 0)) <= 0) {
if ((type == RTM_DELETE && errno == ESRCH) ||
(type == RTM_ADD && errno == EEXIST)) {
log_printf(LOG_DEBUG,
"write() failed in %s on %s : %m", __func__,
strtype);
} else {
log_printf(LOG_WARNING,
"write() failed in %s on %s : %m", __func__,
strtype);
}
goto fail;
}
close(sock);
return 0;
fail:
if (sock >= 0)
close(sock);
return 1;
}
int
in_host_route_add(struct in_addr *dest, struct in_addr *gate,
const char *ifname, int mtu)
{
return in_route0(RTM_ADD, dest, NULL, gate, mtu, ifname, 0);
}
int
in_host_route_delete(struct in_addr *dest, struct in_addr *gate)
{
return in_route0(RTM_DELETE, dest, NULL, gate, 0, NULL, 0);
}
int
in_route_add(struct in_addr *dest, struct in_addr *mask, struct in_addr *gate,
const char *ifname, uint32_t rtm_flags, int mtu)
{
return in_route0(RTM_ADD, dest, mask, gate, mtu, ifname, rtm_flags);
}
int
in_route_delete(struct in_addr *dest, struct in_addr *mask,
struct in_addr *gate, uint32_t rtm_flags)
{
return in_route0(RTM_DELETE, dest, mask, gate, 0, NULL, rtm_flags);
}
int
ip_is_idle_packet(const struct ip * pip, int len)
{
u_int16_t ip_off;
const struct udphdr *uh;
ip_off = ntohs(pip->ip_off);
if ((ip_off & IP_MF) || ((ip_off & IP_OFFMASK) != 0))
return 0;
switch (pip->ip_p) {
case IPPROTO_IGMP:
return 1;
case IPPROTO_ICMP:
if (pip->ip_hl * 4 + 8 > len)
return 1;
switch (((unsigned char *) pip)[pip->ip_hl * 4]) {
case 0:
case 8:
return 0;
default:
return 1;
}
case IPPROTO_UDP:
case IPPROTO_TCP:
uh = (const struct udphdr *) (((const char *) pip) +
(pip->ip_hl * 4));
if (pip->ip_hl * 4 + sizeof(struct udphdr) > len)
return 1;
switch (ntohs(uh->uh_sport)) {
case 53:
case 67:
case 68:
case 123:
case 137:
case 520:
return 1;
}
switch (ntohs(uh->uh_dport)) {
case 53:
case 67:
case 68:
case 123:
case 137:
case 520:
return 1;
}
return 0;
default:
return 0;
}
}
void
in_addr_range_add_route(struct in_addr_range *range)
{
struct in_addr_range *range0;
struct in_addr dest, mask, loop;
for (range0 = range; range0 != NULL; range0 = range0->next){
dest.s_addr = htonl(range0->addr);
mask.s_addr = htonl(range0->mask);
loop.s_addr = htonl(INADDR_LOOPBACK);
in_route_add(&dest, &mask, &loop, LOOPBACK_IFNAME,
RTF_BLACKHOLE, 0);
}
log_printf(LOG_INFO, "Added routes for pooled addresses");
}
void
in_addr_range_delete_route(struct in_addr_range *range)
{
struct in_addr_range *range0;
struct in_addr dest, mask, loop;
for (range0 = range; range0 != NULL; range0 = range0->next){
dest.s_addr = htonl(range0->addr);
mask.s_addr = htonl(range0->mask);
loop.s_addr = htonl(INADDR_LOOPBACK);
in_route_delete(&dest, &mask, &loop, RTF_BLACKHOLE);
}
log_printf(LOG_NOTICE, "Deleted routes for pooled addresses");
}
#undef GETCHAR
#undef GETSHORT
#undef PUTSHORT
#define GETCHAR(c, cp) { (c) = *(cp)++; }
#define GETSHORT(s, cp) { \
(s) = *(cp)++ << 8; \
(s) |= *(cp)++; \
}
#define PUTSHORT(s, cp) { \
*(cp)++ = (u_char) ((s) >> 8); \
*(cp)++ = (u_char) (s); \
}
#define TCP_OPTLEN_IN_SEGMENT 12
#define MAXMSS(mtu) (mtu - sizeof(struct ip) - sizeof(struct tcphdr) - \
TCP_OPTLEN_IN_SEGMENT)
#define ADJUST_CHECKSUM(acc, cksum) { \
acc += cksum; \
if (acc < 0) { \
acc = -acc; \
acc = (acc >> 16) + (acc & 0xffff); \
acc += acc >> 16; \
cksum = (u_short) ~acc; \
} else { \
acc = (acc >> 16) + (acc & 0xffff); \
acc += acc >> 16; \
cksum = (u_short) acc; \
} \
}
int
adjust_tcp_mss(u_char *pktp, int lpktp, int mtu)
{
int opt, optlen, acc, ip_off, mss, maxmss;
struct ip *pip;
struct tcphdr *th;
if (lpktp < sizeof(struct ip) + sizeof(struct tcphdr))
return 1;
pip = (struct ip *)pktp;
ip_off = ntohs(pip->ip_off);
if (pip->ip_p != IPPROTO_TCP || (ip_off & IP_MF) != 0 ||
(ip_off & IP_OFFMASK) != 0)
return 0;
pktp += pip->ip_hl << 2;
lpktp -= pip->ip_hl << 2;
if (sizeof(struct tcphdr) > lpktp)
return 1;
th = (struct tcphdr *)pktp;
if ((th->th_flags & TH_SYN) == 0)
return 0;
lpktp = MINIMUM(th->th_off << 4, lpktp);
pktp += sizeof(struct tcphdr);
lpktp -= sizeof(struct tcphdr);
while (lpktp >= TCPOLEN_MAXSEG) {
GETCHAR(opt, pktp);
switch (opt) {
case TCPOPT_MAXSEG:
GETCHAR(optlen, pktp);
GETSHORT(mss, pktp);
maxmss = MAXMSS(mtu);
if (mss > maxmss) {
pktp-=2;
PUTSHORT(maxmss, pktp);
acc = htons(mss);
acc -= htons(maxmss);
ADJUST_CHECKSUM(acc, th->th_sum);
}
return 0;
break;
case TCPOPT_EOL:
return 0;
break;
case TCPOPT_NOP:
lpktp--;
break;
default:
GETCHAR(optlen, pktp);
if (optlen < 2)
return 1;
pktp += optlen - 2;
lpktp -= optlen;
break;
}
}
return 0;
}