#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/route.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <net/if_dl.h>
#include <stdio.h>
#include <time.h>
#include <event.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <stdlib.h>
#include <stdarg.h>
#include <netdb.h>
#include "slist.h"
#include "debugutil.h"
#include "addr_range.h"
#include "radish.h"
#include "npppd_local.h"
#include "npppd_pool.h"
#include "npppd_subr.h"
#include "net_utils.h"
#ifdef NPPPD_POOL_DEBUG
#define NPPPD_POOL_DBG(x) npppd_pool_log x
#define NPPPD_POOL_ASSERT(cond) \
if (!(cond)) { \
fprintf(stderr, \
"\nASSERT(" #cond ") failed on %s() at %s:%d.\n"\
, __func__, __FILE__, __LINE__); \
abort(); \
}
#else
#define NPPPD_POOL_ASSERT(cond)
#define NPPPD_POOL_DBG(x)
#endif
#define A(v) ((0xff000000 & (v)) >> 24), ((0x00ff0000 & (v)) >> 16), \
((0x0000ff00 & (v)) >> 8), (0x000000ff & (v))
#define SA(sin4) ((struct sockaddr *)(sin4))
#define SHUFLLE_MARK 0xffffffffL
static int npppd_pool_log(npppd_pool *, int, const char *, ...) __printflike(3, 4);
static int is_valid_host_address (uint32_t);
static int npppd_pool_regist_radish(npppd_pool *, struct in_addr_range *,
struct sockaddr_npppd *, int );
int
npppd_pool_init(npppd_pool *_this, npppd *base, const char *name)
{
memset(_this, 0, sizeof(npppd_pool));
strlcpy(_this->ipcp_name, name, sizeof(_this->ipcp_name));
_this->npppd = base;
slist_init(&_this->dyna_addrs);
_this->initialized = 1;
return 0;
}
int
npppd_pool_start(npppd_pool *_this)
{
return 0;
}
void
npppd_pool_uninit(npppd_pool *_this)
{
_this->initialized = 0;
slist_fini(&_this->dyna_addrs);
free(_this->addrs);
_this->addrs = NULL;
_this->addrs_size = 0;
_this->npppd = NULL;
}
int
npppd_pool_reload(npppd_pool *_this)
{
int i, count, addrs_size;
struct sockaddr_npppd *addrs;
struct in_addr_range *pool, *dyna_pool, *range;
char buf0[BUFSIZ], buf1[BUFSIZ];
struct ipcpconf *ipcp;
addrs = NULL;
pool = NULL;
dyna_pool = NULL;
buf0[0] = '\0';
TAILQ_FOREACH(ipcp, &_this->npppd->conf.ipcpconfs, entry) {
if (strcmp(ipcp->name, _this->ipcp_name) == 0) {
dyna_pool = ipcp->dynamic_pool;
pool = ipcp->static_pool;
}
}
addrs_size = 0;
for (range = dyna_pool; range != NULL; range = range->next)
addrs_size++;
for (range = pool; range != NULL; range = range->next)
addrs_size++;
if ((addrs = calloc(addrs_size + 1, sizeof(struct sockaddr_npppd)))
== NULL) {
npppd_pool_log(_this, LOG_WARNING,
"calloc() failed in %s: %m", __func__);
goto fail;
}
count = 0;
for (i = 0, range = dyna_pool; range != NULL; range = range->next, i++){
if (npppd_pool_regist_radish(_this, range, &addrs[count], 1))
goto fail;
if (count == 0)
strlcat(buf0, "dyn_pool=[", sizeof(buf0));
else
strlcat(buf0, ",", sizeof(buf0));
snprintf(buf1, sizeof(buf1), "%d.%d.%d.%d/%d",
A(range->addr), netmask2prefixlen(range->mask));
strlcat(buf0, buf1, sizeof(buf0));
count++;
}
if (i > 0)
strlcat(buf0, "] ", sizeof(buf0));
for (i = 0, range = pool; range != NULL; range = range->next, i++) {
if (npppd_pool_regist_radish(_this, range, &addrs[count], 0))
goto fail;
if (i == 0)
strlcat(buf0, "pool=[", sizeof(buf0));
else
strlcat(buf0, ",", sizeof(buf0));
snprintf(buf1, sizeof(buf1), "%d.%d.%d.%d/%d",
A(range->addr), netmask2prefixlen(range->mask));
strlcat(buf0, buf1, sizeof(buf0));
count++;
}
if (i > 0)
strlcat(buf0, "]", sizeof(buf0));
npppd_pool_log(_this, LOG_INFO, "%s", buf0);
count = 0;
slist_add(&_this->dyna_addrs, (void *)SHUFLLE_MARK);
for (range = dyna_pool; range != NULL; range = range->next) {
if (count >= NPPPD_MAX_POOLED_ADDRS)
break;
for (i = 0; i <= ~(range->mask); i++) {
if (!is_valid_host_address(range->addr + i))
continue;
if (count >= NPPPD_MAX_POOLED_ADDRS)
break;
slist_add(&_this->dyna_addrs,
(void *)(uintptr_t)(range->addr + i));
count++;
}
}
free(_this->addrs);
_this->addrs = addrs;
_this->addrs_size = addrs_size;
return 0;
fail:
free(addrs);
return 1;
}
static int
npppd_pool_regist_radish(npppd_pool *_this, struct in_addr_range *range,
struct sockaddr_npppd *snp, int is_dynamic)
{
int rval;
struct sockaddr_in sin4a, sin4b;
struct sockaddr_npppd *snp0;
npppd_pool *npool0;
memset(&sin4a, 0, sizeof(sin4a));
memset(&sin4b, 0, sizeof(sin4b));
sin4a.sin_len = sin4b.sin_len = sizeof(sin4a);
sin4a.sin_family = sin4b.sin_family = AF_INET;
sin4a.sin_addr.s_addr = htonl(range->addr);
sin4b.sin_addr.s_addr = htonl(range->mask);
snp->snp_len = sizeof(struct sockaddr_npppd);
snp->snp_family = AF_INET;
snp->snp_addr.s_addr = htonl(range->addr);
snp->snp_mask.s_addr = htonl(range->mask);
snp->snp_data_ptr = _this;
if (is_dynamic)
snp->snp_type = SNP_DYN_POOL;
else
snp->snp_type = SNP_POOL;
if ((snp0 = rd_lookup(SA(&sin4a), SA(&sin4b),
_this->npppd->rd)) != NULL) {
NPPPD_POOL_ASSERT(snp0->snp_type != SNP_PPP);
npool0 = snp0->snp_data_ptr;
if (!is_dynamic && npool0 == _this)
return 0;
npppd_pool_log(_this, LOG_WARNING,
"%d.%d.%d.%d/%d is already defined as '%s'(%s)",
A(range->addr), netmask2prefixlen(range->mask),
npool0->ipcp_name, (snp0->snp_type == SNP_POOL)
? "static" : "dynamic");
goto fail;
}
if ((rval = rd_insert(SA(&sin4a), SA(&sin4b), _this->npppd->rd,
snp)) != 0) {
errno = rval;
npppd_pool_log(_this, LOG_WARNING,
"rd_insert(%d.%d.%d.%d/%d) failed: %m",
A(range->addr), netmask2prefixlen(range->mask));
goto fail;
}
return 0;
fail:
return 1;
}
uint32_t
npppd_pool_get_dynamic(npppd_pool *_this, npppd_ppp *ppp)
{
int shuffle_cnt;
uintptr_t result = 0;
struct sockaddr_npppd *snp;
npppd_ppp *ppp0;
shuffle_cnt = 0;
slist_itr_first(&_this->dyna_addrs);
while (slist_length(&_this->dyna_addrs) > 1 &&
slist_itr_has_next(&_this->dyna_addrs)) {
result = (uintptr_t)slist_itr_next(&_this->dyna_addrs);
if (result == 0)
break;
if ((uint32_t)result == SHUFLLE_MARK) {
if (shuffle_cnt++ > 0) {
result = 0;
break;
}
NPPPD_POOL_DBG((_this, LOG_DEBUG, "shuffle"));
slist_itr_remove(&_this->dyna_addrs);
slist_shuffle(&_this->dyna_addrs);
slist_add(&_this->dyna_addrs, (void *)result);
slist_itr_first(&_this->dyna_addrs);
continue;
}
slist_itr_remove(&_this->dyna_addrs);
switch (npppd_pool_get_assignability(_this, (uint32_t)result,
0xffffffffL, &snp)) {
case ADDRESS_OK:
return (uint32_t)result;
default:
continue;
case ADDRESS_BUSY:
NPPPD_POOL_ASSERT(snp != NULL);
NPPPD_POOL_ASSERT(snp->snp_type == SNP_PPP);
ppp0 = snp->snp_data_ptr;
ppp0->assigned_pool = _this;
ppp0->assign_dynapool = 1;
continue;
}
break;
}
return (uint32_t)0;
}
static inline int
npppd_is_ifcace_ip4addr(npppd *_this, uint32_t ip4addr)
{
int i;
for (i = 0; i < countof(_this->iface); i++) {
if (npppd_iface_ip_is_ready(&_this->iface[i]) &&
_this->iface[i].ip4addr.s_addr == ip4addr)
return 1;
}
return 0;
}
int
npppd_pool_assign_ip(npppd_pool *_this, npppd_ppp *ppp)
{
int rval;
uint32_t ip4;
void *rtent;
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_len = sizeof(struct sockaddr_in)
}, mask = {
.sin_family = AF_INET,
.sin_len = sizeof(struct sockaddr_in),
};
struct sockaddr_npppd *snp;
ip4 = ntohl(ppp->ppp_framed_ip_address.s_addr);
slist_itr_first(&_this->dyna_addrs);
while (slist_itr_has_next(&_this->dyna_addrs)) {
if ((uintptr_t)slist_itr_next(&_this->dyna_addrs) != ip4)
continue;
slist_itr_remove(&_this->dyna_addrs);
break;
}
addr.sin_addr = ppp->ppp_framed_ip_address;
mask.sin_addr = ppp->ppp_framed_ip_netmask;
addr.sin_addr.s_addr &= mask.sin_addr.s_addr;
if (rd_delete(SA(&addr), SA(&mask), _this->npppd->rd, &rtent) == 0) {
snp = rtent;
NPPPD_POOL_ASSERT(snp != NULL);
NPPPD_POOL_ASSERT(snp->snp_type != SNP_PPP);
ppp->snp.snp_next = snp;
NPPPD_POOL_DBG((_this, DEBUG_LEVEL_2,
"pool %s/32 => %s(ppp=%d)",
inet_ntoa(ppp->ppp_framed_ip_address), ppp->username,
ppp->id));
}
NPPPD_POOL_DBG((_this, LOG_DEBUG, "rd_insert(%s) %s",
inet_ntoa(addr.sin_addr), ppp->username));
if ((rval = rd_insert((struct sockaddr *)&addr,
(struct sockaddr *)&mask, _this->npppd->rd, &ppp->snp)) != 0) {
errno = rval;
log_printf(LOG_INFO, "rd_insert(%s) failed: %m",
inet_ntoa(ppp->ppp_framed_ip_address));
return 1;
}
return 0;
}
void
npppd_pool_release_ip(npppd_pool *_this, npppd_ppp *ppp)
{
void *item;
int rval;
struct sockaddr_npppd *snp;
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_len = sizeof(struct sockaddr_in)
}, mask = {
.sin_family = AF_INET,
.sin_len = sizeof(struct sockaddr_in),
};
if (!ppp_ip_assigned(ppp))
return;
addr.sin_addr = ppp->ppp_framed_ip_address;
mask.sin_addr = ppp->ppp_framed_ip_netmask;
addr.sin_addr.s_addr &= mask.sin_addr.s_addr;
if ((rval = rd_delete((struct sockaddr *)&addr,
(struct sockaddr *)&mask, ppp->pppd->rd, &item)) != 0) {
errno = rval;
log_printf(LOG_INFO, "Unexpected error: "
"rd_delete(%s) failed: %m",
inet_ntoa(ppp->ppp_framed_ip_address));
}
snp = item;
if (_this != NULL && ppp->assign_dynapool != 0) {
NPPPD_POOL_ASSERT(_this == ppp->assigned_pool);
slist_add(&((npppd_pool *)ppp->assigned_pool)->dyna_addrs,
(void *)(uintptr_t)ntohl(
ppp->ppp_framed_ip_address.s_addr));
}
if (snp != NULL && snp->snp_next != NULL) {
if (rd_insert(SA(&addr), SA(&mask), ppp->pppd->rd,
snp->snp_next) != 0) {
log_printf(LOG_INFO, "Unexpected error: "
"rd_insert(%s) failed: %m",
inet_ntoa(ppp->ppp_framed_ip_address));
}
NPPPD_POOL_DBG((_this, DEBUG_LEVEL_2,
"pool %s/%d <= %s(ppp=%d)",
inet_ntoa(ppp->ppp_framed_ip_address),
netmask2prefixlen(ntohl(ppp->ppp_framed_ip_netmask.s_addr)),
ppp->username, ppp->id));
snp->snp_next = NULL;
}
}
int
npppd_pool_get_assignability(npppd_pool *_this, uint32_t ip4addr,
uint32_t ip4mask, struct sockaddr_npppd **psnp)
{
struct radish *radish;
struct sockaddr_in sin4;
struct sockaddr_npppd *snp;
NPPPD_POOL_ASSERT(ip4mask != 0);
NPPPD_POOL_DBG((_this, LOG_DEBUG, "%s(%08x,%08x)", __func__, ip4addr,
ip4mask));
if (netmask2prefixlen(htonl(ip4mask)) == 32) {
if (!is_valid_host_address(ip4addr))
return ADDRESS_INVALID;
}
memset(&sin4, 0, sizeof(sin4));
sin4.sin_len = sizeof(sin4);
sin4.sin_family = AF_INET;
sin4.sin_addr.s_addr = htonl(ip4addr);
if (npppd_is_ifcace_ip4addr(_this->npppd, sin4.sin_addr.s_addr))
return ADDRESS_RESERVED;
if (rd_match(SA(&sin4), _this->npppd->rd, &radish)) {
do {
snp = radish->rd_rtent;
if (snp->snp_type == SNP_POOL ||
snp->snp_type == SNP_DYN_POOL) {
if (psnp != NULL)
*psnp = snp;
if (snp->snp_data_ptr == _this)
return ADDRESS_OK;
else
return ADDRESS_RESERVED;
}
if (snp->snp_type == SNP_PPP) {
if (psnp != NULL)
*psnp = snp;
return ADDRESS_BUSY;
}
} while (rd_match_next(SA(&sin4), _this->npppd->rd, &radish,
radish));
}
return ADDRESS_OUT_OF_POOL;
}
static int
is_valid_host_address(uint32_t addr)
{
if (IN_CLASSA(addr))
return ((IN_CLASSA_HOST & addr) == 0 ||
(IN_CLASSA_HOST & addr) == IN_CLASSA_HOST)? 0 : 1;
if (IN_CLASSB(addr))
return ((IN_CLASSB_HOST & addr) == 0 ||
(IN_CLASSB_HOST & addr) == IN_CLASSB_HOST)? 0 : 1;
if (IN_CLASSC(addr))
return ((IN_CLASSC_HOST & addr) == 0 ||
(IN_CLASSC_HOST & addr) == IN_CLASSC_HOST)? 0 : 1;
return 0;
}
static int
npppd_pool_log(npppd_pool *_this, int prio, const char *fmt, ...)
{
int status;
char logbuf[BUFSIZ];
va_list ap;
va_start(ap, fmt);
snprintf(logbuf, sizeof(logbuf), "ipcp=%s pool %s",
(_this == NULL)? "null" : _this->ipcp_name, fmt);
status = vlog_printf(prio, logbuf, ap);
va_end(ap);
return status;
}