#include <atomic.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <libdllink.h>
#include <libscf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <libnwam.h>
#include "known_wlans.h"
#include "llp.h"
#include "ncu.h"
#include "util.h"
#define OUR_OLD_DHCP_WAIT_TIME_PROP_NAME "dhcp_wait_time"
#define OUR_OLD_USE_NET_SVC_PROP_NAME "use_net_svc"
#define OUR_OLD_IDLE_TIME_PROP_NAME "idle_time"
static struct qelem llp_list;
static uint32_t llp_highest_pri;
static boolean_t static_configured = B_FALSE;
static enum interface_type
find_if_type(const char *name)
{
uint32_t media;
enum interface_type type;
if (name == NULL) {
nlog(LOG_DEBUG, "find_if_type: no ifname; "
"returning IF_UNKNOWN");
return (IF_UNKNOWN);
}
type = IF_WIRED;
if (dladm_name2info(dld_handle, name, NULL, NULL, NULL, &media) !=
DLADM_STATUS_OK) {
if (strncmp(name, "ip.tun", 6) == 0 ||
strncmp(name, "ip6.tun", 7) == 0 ||
strncmp(name, "ip.6to4tun", 10) == 0)
type = IF_TUN;
} else if (media == DL_WIFI) {
type = IF_WIRELESS;
}
return (type);
}
static void
llp_list_free(void)
{
llp_t *llp;
while (llp_list.q_forw != &llp_list) {
llp = (llp_t *)llp_list.q_forw;
remque(&llp->llp_links);
free(llp->llp_ipv6addrstr);
free(llp->llp_ipv4addrstr);
free(llp);
}
}
static void
initialize_llp(void)
{
llp_list.q_forw = llp_list.q_back = &llp_list;
}
static llp_t *
llp_lookup(const char *link)
{
llp_t *llp;
if (link == NULL)
return (NULL);
for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list;
llp = (llp_t *)llp->llp_links.q_forw) {
if (strcmp(link, llp->llp_lname) == 0)
break;
}
if (llp == (llp_t *)&llp_list)
llp = NULL;
return (llp);
}
static llp_t *
llp_add(const char *name)
{
llp_t *llp;
if ((llp = calloc(1, sizeof (llp_t))) == NULL) {
nlog(LOG_ERR, "llp_add: cannot allocate LLP: %m");
return (NULL);
}
if (strlcpy(llp->llp_lname, name, sizeof (llp->llp_lname)) >=
sizeof (llp->llp_lname)) {
nlog(LOG_ERR, "llp_add: linkname '%s' too long; ignoring entry",
name);
free(llp);
return (NULL);
}
llp->llp_fileorder = llp->llp_pri =
atomic_add_32_nv(&llp_highest_pri, 1);
llp->llp_ipv4src = IPV4SRC_DHCP;
llp->llp_type = find_if_type(llp->llp_lname);
llp->llp_ipv6onlink = B_TRUE;
if (llp->llp_type != IF_WIRED && llp->llp_type != IF_WIRELESS) {
nlog(LOG_ERR, "llp_add: wrong type of interface for %s", name);
free(llp);
return (NULL);
}
insque(&llp->llp_links, llp_list.q_back);
nlog(LOG_DEBUG, "llp_add: "
"created llp for link %s, priority %d", llp->llp_lname,
llp->llp_pri);
return (llp);
}
static int
parse_llp_config(void)
{
static const char STATICSTR[] = "static";
static const char DHCP[] = "dhcp";
static const char IPV6[] = "ipv6";
static const char NOIPV6[] = "noipv6";
static const char PRIORITY[] = "priority";
FILE *fp;
char line[LINE_MAX];
char *cp, *lasts, *lstr, *srcstr, *addrstr;
int lnum;
llp_t *llp;
initialize_llp();
fp = fopen(LLPFILE, "r+");
if (fp == NULL) {
if (errno == ENOENT)
return (errno);
nlog(LOG_ERR, "parse_llp_config: "
"open legacy LLP config file: %m");
return (-1);
}
for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) {
if (line[strlen(line) - 1] == '\n')
line[strlen(line) - 1] = '\0';
cp = line;
while (isspace(*cp))
cp++;
if (*cp == '#' || *cp == '\0')
continue;
nlog(LOG_DEBUG, "parse_llp_config: "
"parsing legacy LLP conf file line %d...", lnum);
if (((lstr = strtok_r(cp, " \t", &lasts)) == NULL) ||
((srcstr = strtok_r(NULL, " \t", &lasts)) == NULL)) {
nlog(LOG_ERR, "parse_llp_config: line %d: "
"not enough tokens; ignoring entry", lnum);
continue;
}
if ((llp = llp_lookup(lstr)) == NULL &&
(llp = llp_add(lstr)) == NULL) {
nlog(LOG_ERR, "parse_llp_config: line %d: "
"cannot add entry", lnum);
continue;
}
if (strcasecmp(srcstr, STATICSTR) == 0) {
if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL ||
atoi(addrstr) == 0) {
nlog(LOG_ERR, "parse_llp_config: line %d: "
"missing ipaddr for static config", lnum);
} else if ((addrstr = strdup(addrstr)) == NULL) {
nlog(LOG_ERR, "parse_llp_config: line %d: "
"cannot save address", lnum);
} else {
free(llp->llp_ipv4addrstr);
llp->llp_ipv4src = IPV4SRC_STATIC;
llp->llp_ipv4addrstr = addrstr;
}
} else if (strcasecmp(srcstr, DHCP) == 0) {
llp->llp_ipv4src = IPV4SRC_DHCP;
} else if (strcasecmp(srcstr, IPV6) == 0) {
llp->llp_ipv6onlink = B_TRUE;
if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) {
(void) 0;
} else if ((addrstr = strdup(addrstr)) == NULL) {
nlog(LOG_ERR, "parse_llp_config: line %d: "
"cannot save address", lnum);
} else {
free(llp->llp_ipv6addrstr);
llp->llp_ipv6addrstr = addrstr;
}
} else if (strcasecmp(srcstr, NOIPV6) == 0) {
llp->llp_ipv6onlink = B_FALSE;
} else if (strcasecmp(srcstr, PRIORITY) == 0) {
if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) {
nlog(LOG_ERR,
"parse_llp_config: line %d: "
"missing priority value", lnum);
} else {
llp->llp_pri = atoi(addrstr);
}
} else {
nlog(LOG_ERR, "parse_llp_config: line %d: "
"unrecognized field '%s'", lnum, srcstr);
}
}
(void) fclose(fp);
return (0);
}
static int
upgrade_llp_config(void)
{
llp_t *wp;
nwam_ncp_handle_t user_ncp;
nwam_ncu_handle_t phys_ncu = NULL, ip_ncu = NULL;
nwam_error_t err;
uint64_t uintval;
char *strval;
const char *prop;
switch (parse_llp_config()) {
case -1:
return (0);
case ENOENT:
return (ENOENT);
default:
break;
}
err = nwam_ncp_create(NWAM_NCP_NAME_USER, 0, &user_ncp);
switch (err) {
case NWAM_SUCCESS:
break;
case NWAM_ERROR_BIND:
case NWAM_ERROR_INTERNAL:
nlog(LOG_ERR, "upgrade_llp_config: "
"could not create User NCP: %s", nwam_strerror(err));
llp_list_free();
return (EAGAIN);
default:
nlog(LOG_ERR, "upgrade_llp_config: error creating User NCP: %s",
nwam_strerror(err));
llp_list_free();
return (0);
}
nlog(LOG_DEBUG, "upgrade_llp_config: walking llp list");
for (wp = (llp_t *)llp_list.q_forw; wp != (llp_t *)&llp_list;
wp = (llp_t *)wp->llp_links.q_forw) {
nlog(LOG_DEBUG, "upgrade_llp_config: "
"upgrading llp %s", wp->llp_lname);
if (nwam_ncu_create(user_ncp, wp->llp_lname,
NWAM_NCU_TYPE_INTERFACE, NWAM_NCU_CLASS_IP, &ip_ncu)
!= NWAM_SUCCESS ||
nwam_ncu_create(user_ncp, wp->llp_lname, NWAM_NCU_TYPE_LINK,
NWAM_NCU_CLASS_PHYS, &phys_ncu) != NWAM_SUCCESS) {
nlog(LOG_ERR, "upgrade_llp_config: llp %s: "
"could not create NCUs: %s", wp->llp_lname,
nwam_strerror(err));
break;
}
prop = NWAM_NCU_PROP_ACTIVATION_MODE;
uintval = NWAM_ACTIVATION_MODE_PRIORITIZED;
if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop))
!= NWAM_SUCCESS)
break;
prop = NWAM_NCU_PROP_PRIORITY_MODE;
uintval = NWAM_PRIORITY_MODE_EXCLUSIVE;
if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop))
!= NWAM_SUCCESS)
break;
prop = NWAM_NCU_PROP_PRIORITY_GROUP;
uintval = wp->llp_pri;
if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop))
!= NWAM_SUCCESS)
break;
if (wp->llp_ipv4addrstr != NULL) {
prop = NWAM_NCU_PROP_IPV4_ADDRSRC;
uintval = NWAM_ADDRSRC_STATIC;
if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1,
prop)) != NWAM_SUCCESS)
break;
prop = NWAM_NCU_PROP_IPV4_ADDR;
strval = wp->llp_ipv4addrstr;
if ((err = nwamd_set_ncu_string(ip_ncu, &strval, 1,
prop)) != NWAM_SUCCESS)
break;
static_configured = B_TRUE;
}
if (wp->llp_ipv6addrstr != NULL) {
prop = NWAM_NCU_PROP_IPV6_ADDRSRC;
uintval = NWAM_ADDRSRC_STATIC;
if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1,
prop)) != NWAM_SUCCESS)
break;
prop = NWAM_NCU_PROP_IPV6_ADDR;
strval = wp->llp_ipv6addrstr;
if ((err = nwamd_set_ncu_string(ip_ncu, &strval, 1,
prop)) != NWAM_SUCCESS)
break;
static_configured = B_TRUE;
}
if (!wp->llp_ipv6onlink) {
prop = NWAM_NCU_PROP_IP_VERSION;
uintval = IPV4_VERSION;
if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1,
prop)) != NWAM_SUCCESS)
break;
}
if ((err = nwam_ncu_commit(ip_ncu, 0)) != NWAM_SUCCESS ||
(err = nwam_ncu_commit(phys_ncu, 0)) != NWAM_SUCCESS) {
nlog(LOG_ERR, "upgrade_llp_config: llp %s: "
"could not commit NCUs: %s", wp->llp_lname,
nwam_strerror(err));
llp_list_free();
nwam_ncu_free(ip_ncu);
nwam_ncu_free(phys_ncu);
(void) nwam_ncp_destroy(user_ncp, 0);
return (EAGAIN);
}
}
if (err != NWAM_SUCCESS) {
nlog(LOG_ERR, "upgrade_llp_config: llp %s: "
"could not set value for property %s: %s", wp->llp_lname,
prop, nwam_strerror(err));
}
llp_list_free();
nwam_ncu_free(ip_ncu);
nwam_ncu_free(phys_ncu);
nwam_ncp_free(user_ncp);
return (0);
}
void
nwamd_handle_upgrade(nwamd_event_t event)
{
nwamd_event_t upgrade_event;
uint64_t dhcp_wait_time, idle_time;
boolean_t use_net_svc;
switch (upgrade_llp_config()) {
case -1:
case ENOENT:
break;
case EAGAIN:
upgrade_event = nwamd_event_init(NWAM_EVENT_TYPE_UPGRADE,
NWAM_OBJECT_TYPE_NCP, 0, NULL);
if (upgrade_event == NULL) {
nlog(LOG_ERR, "nwamd_handle_upgrade: "
"could not create retry event to upgrade "
"%s configuration", LLPFILE);
return;
}
nwamd_event_enqueue_timed(upgrade_event,
NWAMD_READONLY_RETRY_INTERVAL);
return;
default:
break;
}
if (static_configured) {
nlog(LOG_DEBUG, "nwamd_handle_upgrade: "
"static address configured, enabling User NCP");
(void) pthread_mutex_lock(&active_ncp_mutex);
(void) strlcpy(active_ncp, NWAM_NCP_NAME_USER,
NWAM_MAX_NAME_LEN);
(void) pthread_mutex_unlock(&active_ncp_mutex);
}
upgrade_known_wifi_nets_config();
if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
OUR_OLD_DHCP_WAIT_TIME_PROP_NAME, &dhcp_wait_time) == 0) {
(void) nwamd_set_count_property(OUR_FMRI, OUR_PG,
OUR_NCU_WAIT_TIME_PROP_NAME, dhcp_wait_time);
(void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG,
OUR_OLD_DHCP_WAIT_TIME_PROP_NAME);
nlog(LOG_DEBUG, "nwamd_handle_upgrade: "
"converted '%s' to '%s' with value of %lld",
OUR_OLD_DHCP_WAIT_TIME_PROP_NAME,
OUR_NCU_WAIT_TIME_PROP_NAME, dhcp_wait_time);
}
if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
OUR_OLD_IDLE_TIME_PROP_NAME, &idle_time) == 0) {
(void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG,
OUR_OLD_IDLE_TIME_PROP_NAME);
}
if (nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG,
OUR_OLD_USE_NET_SVC_PROP_NAME, &use_net_svc) == 0) {
(void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG,
OUR_OLD_USE_NET_SVC_PROP_NAME);
}
nlog(LOG_DEBUG, "nwamd_handle_upgrade: "
"creating version property, setting to 1\n");
(void) nwamd_set_count_property(OUR_FMRI, OUR_PG,
OUR_VERSION_PROP_NAME, 1U);
(void) smf_refresh_instance(OUR_FMRI);
}