#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <net/if.h>
#include <net/route.h>
#include <sys/sockio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <pthread.h>
#include <syslog.h>
#include <smbsrv/libsmb.h>
#include "smbd.h"
#define SMBD_NICMON_ENABLE "nicmon_enable"
#define SMBD_NICMON_THROTTLE 100
#define SMBD_NICMON_DEBOUNCE 2
extern smbd_t smbd;
static boolean_t smbd_nicmon_enabled = B_TRUE;
static int eventpipe_write = -1;
static char *smbd_nicmon_caller_fmri = NULL;
static void smbd_nicmon_run_check(void);
static int smbd_nicmon_setup_rtsock(int);
static int smbd_nicmon_needscan(int);
static int smbd_nicmon_setup_eventpipe(int *, int *);
static void *smbd_nicmon_daemon(void *);
int
smbd_nicmon_start(const char *svc_fmri)
{
pthread_t smbd_nicmon_tid;
int rc;
if (smb_nic_init() != SMB_NIC_SUCCESS)
return (-1);
rc = pthread_create(&smbd_nicmon_tid, NULL, smbd_nicmon_daemon, NULL);
if (rc != 0)
return (-1);
if (svc_fmri)
smbd_nicmon_caller_fmri = (char *)svc_fmri;
smbd_nicmon_run_check();
return (0);
}
void
smbd_nicmon_stop(void)
{
uchar_t buf = 1;
if (eventpipe_write < 0)
return;
(void) write(eventpipe_write, &buf, sizeof (buf));
smbd_nicmon_caller_fmri = NULL;
smb_nic_fini();
}
int
smbd_nicmon_refresh(void)
{
if (smb_nic_init() != SMB_NIC_SUCCESS)
return (-1);
smbd_nicmon_run_check();
return (0);
}
static void
smbd_nicmon_run_check(void)
{
smb_scfhandle_t *hd;
uint8_t status;
int rc;
smbd_nicmon_enabled = B_TRUE;
if ((hd = smb_smf_scf_init(SMBD_FMRI_PREFIX)) == NULL) {
syslog(LOG_DEBUG,
"smbd_nicmon: smb_smf_scf_init failed");
return;
}
rc = smb_smf_create_service_pgroup(hd, SMBD_PG_NAME);
if (rc != SMBD_SMF_OK) {
smb_smf_scf_fini(hd);
syslog(LOG_DEBUG,
"smbd_nicmon: smb_smf_create_service_pgroup failed");
return;
}
rc = smb_smf_get_boolean_property(hd, SMBD_NICMON_ENABLE, &status);
if (rc == SMBD_SMF_OK && status == 0)
smbd_nicmon_enabled = B_FALSE;
smb_smf_scf_fini(hd);
}
static int
smbd_nicmon_setup_rtsock(int af)
{
int sd;
int flags;
if ((sd = socket(PF_ROUTE, SOCK_RAW, af)) == -1) {
syslog(LOG_ERR,
"smbd_nicmon: routing socket failed: %d", errno);
return (-1);
}
if ((flags = fcntl(sd, F_GETFL, 0)) < 0) {
syslog(LOG_ERR,
"smbd_nicmon: fcntl F_GETFL failed: %d", errno);
(void) close(sd);
return (-1);
}
if ((fcntl(sd, F_SETFL, flags | O_NONBLOCK)) < 0) {
syslog(LOG_ERR,
"smbd_nicmon: fcntl F_SETFL failed: %d", errno);
(void) close(sd);
return (-1);
}
return (sd);
}
static int
smbd_nicmon_needscan(int sock)
{
static uint32_t throttle;
struct rt_msghdr *rtm;
int64_t msg[2048 / 8];
int need_if_scan = 0;
int nbytes;
for (;;) {
nbytes = read(sock, msg, sizeof (msg));
if (nbytes <= 0)
break;
rtm = (struct rt_msghdr *)msg;
if (rtm->rtm_version != RTM_VERSION)
continue;
if (nbytes < rtm->rtm_msglen) {
if ((throttle % SMBD_NICMON_THROTTLE) == 0) {
syslog(LOG_DEBUG,
"smbd_nicmon: short read: %d of %d",
nbytes, rtm->rtm_msglen);
}
++throttle;
continue;
}
switch (rtm->rtm_type) {
case RTM_NEWADDR:
case RTM_DELADDR:
case RTM_IFINFO:
need_if_scan = 1;
break;
default:
break;
}
}
return (need_if_scan);
}
static int
smbd_nicmon_setup_eventpipe(int *read_pipe, int *write_pipe)
{
int fds[2];
if ((pipe(fds)) < 0) {
syslog(LOG_ERR,
"smbd_nicmon: event pipe failed: %d", errno);
return (-1);
}
*read_pipe = fds[0];
*write_pipe = fds[1];
return (0);
}
static void *
smbd_nicmon_daemon(void *arg)
{
static uint32_t throttle;
static int rtsock_v4;
static int eventpipe_read = -1;
struct pollfd pollfds[2];
int pollfd_num = 2;
int i, nic_changed;
int rc;
if ((rtsock_v4 = smbd_nicmon_setup_rtsock(AF_INET)) == -1)
return (NULL);
rc = smbd_nicmon_setup_eventpipe(&eventpipe_read, &eventpipe_write);
if (rc != 0)
return (NULL);
for (;;) {
errno = 0;
nic_changed = 0;
pollfds[0].fd = rtsock_v4;
pollfds[0].events = POLLIN;
pollfds[1].fd = eventpipe_read;
pollfds[1].events = POLLIN;
if (poll(pollfds, pollfd_num, -1) < 0) {
if (errno == EINTR)
continue;
if ((throttle % SMBD_NICMON_THROTTLE) == 0)
syslog(LOG_DEBUG,
"smbd_nicmon: poll failed: %d", errno);
++throttle;
break;
}
for (i = 0; i < pollfd_num; i++) {
if ((pollfds[i].fd < 0) ||
!(pollfds[i].revents & POLLIN))
continue;
if (pollfds[i].fd == rtsock_v4) {
(void) sleep(SMBD_NICMON_DEBOUNCE);
nic_changed = smbd_nicmon_needscan(rtsock_v4);
}
if (pollfds[i].fd == eventpipe_read)
goto done;
}
if (smbd_nicmon_enabled && nic_changed &&
smbd_nicmon_caller_fmri) {
if (smf_refresh_instance(smbd_nicmon_caller_fmri) != 0)
syslog(LOG_ERR,
"smbd_nicmon: %s refresh failed",
smbd_nicmon_caller_fmri);
}
}
done:
(void) close(rtsock_v4);
(void) close(eventpipe_read);
(void) close(eventpipe_write);
eventpipe_write = -1;
return (NULL);
}