#include "eventmon.h"
#include "cache.h"
#include "ev_dlil.h"
#include "ev_ipv4.h"
#include "ev_ipv6.h"
#include <notify.h>
#include <sys/sysctl.h>
#include <sys/kern_event.h>
#include <network/config.h>
static dispatch_queue_t S_kev_queue;
static dispatch_source_t S_kev_source;
__private_extern__ Boolean network_changed = FALSE;
__private_extern__ SCDynamicStoreRef store = NULL;
__private_extern__ Boolean _verbose = FALSE;
__private_extern__ os_log_t
__log_KernelEventMonitor()
{
static os_log_t log = NULL;
if (log == NULL) {
log = os_log_create("com.apple.SystemConfiguration", "KernelEventMonitor");
}
return log;
}
#define MESSAGES_MAX 100
static CFMutableArrayRef S_messages;
static Boolean S_messages_modified;
static void
messages_init(void)
{
S_messages = CFArrayCreateMutable(NULL,
0,
&kCFTypeArrayCallBacks);
return;
}
static void
messages_free(void)
{
if (S_messages != NULL) {
CFRelease(S_messages);
S_messages = NULL;
}
return;
}
static Boolean
messages_should_add_message(void)
{
if (S_messages == NULL
|| CFArrayGetCount(S_messages) >= MESSAGES_MAX) {
return (FALSE);
}
return (TRUE);
}
static void
messages_add_message(CFStringRef message)
{
if (messages_should_add_message()) {
CFArrayAppendValue(S_messages, message);
S_messages_modified = TRUE;
}
return;
}
__private_extern__ void
messages_add_msg_with_arg(const char * msg, const char * arg)
{
if (messages_should_add_message()) {
CFStringRef str;
str = CFStringCreateWithFormat(NULL, NULL,
CFSTR("%12.8f: %s %s"),
CFAbsoluteTimeGetCurrent(),
msg, arg);
messages_add_message(str);
CFRelease(str);
}
return;
}
static void
messages_post(void)
{
if (S_messages != NULL && S_messages_modified) {
SCDynamicStoreSetValue(NULL,
CFSTR("Plugin:KernelEventMonitor"),
S_messages);
S_messages_modified = FALSE;
}
return;
}
static void
check_interface_link_status(const char * if_name)
{
if (S_messages == NULL) {
return;
}
link_update_status_if_missing(if_name);
return;
}
__private_extern__
int
dgram_socket(int domain)
{
int s;
s = socket(domain, SOCK_DGRAM, 0);
if (s == -1) {
SC_log(LOG_ERR, "socket() failed: %s", strerror(errno));
}
return s;
}
static int
ifflags_set(int s, char * name, short flags)
{
struct ifreq ifr;
int ret;
bzero(&ifr, sizeof(ifr));
strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
ret = ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr);
if (ret == -1) {
return (ret);
}
ifr.ifr_flags |= flags;
return (ioctl(s, SIOCSIFFLAGS, &ifr));
}
static int
ifflags_clear(int s, char * name, short flags)
{
struct ifreq ifr;
int ret;
bzero(&ifr, sizeof(ifr));
strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
ret = ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr);
if (ret == -1) {
return (ret);
}
ifr.ifr_flags &= ~flags;
return (ioctl(s, SIOCSIFFLAGS, &ifr));
}
static void
mark_if_up(char * name)
{
int s = dgram_socket(AF_INET);
if (s == -1)
return;
ifflags_set(s, name, IFF_UP);
close(s);
}
static void
mark_if_down(char * name)
{
int s = dgram_socket(AF_INET);
if (s == -1)
return;
ifflags_clear(s, name, IFF_UP);
close(s);
}
static void
post_network_changed(void)
{
if (network_changed) {
uint32_t status;
status = notify_post(_SC_NOTIFY_NETWORK_CHANGE);
if (status != NOTIFY_STATUS_OK) {
SC_log(LOG_NOTICE, "notify_post() failed: error=%u", status);
}
network_changed = FALSE;
}
return;
}
static void
logEvent(CFStringRef evStr, struct kern_event_msg *ev_msg)
{
int i;
int j;
if (!_verbose) {
return;
}
SC_log(LOG_DEBUG, "%@ event:", evStr);
SC_log(LOG_DEBUG, " Event size=%d, id=%d, vendor=%d, class=%d, subclass=%d, code=%d",
ev_msg->total_size,
ev_msg->id,
ev_msg->vendor_code,
ev_msg->kev_class,
ev_msg->kev_subclass,
ev_msg->event_code);
for (i = 0, j = KEV_MSG_HEADER_SIZE; j < ev_msg->total_size; i++, j+=4) {
SC_log(LOG_DEBUG, " Event data[%2d] = %08x", i, ev_msg->event_data[i]);
}
}
static void
copy_if_name(const struct net_event_data * ev, char * ifr_name, int ifr_len)
{
snprintf(ifr_name, ifr_len, "%s%d", ev->if_name, ev->if_unit);
return;
}
static uint8_t info_zero[DLIL_MODARGLEN];
static void
processEvent_Apple_Network(struct kern_event_msg *ev_msg)
{
int dataLen = (ev_msg->total_size - KEV_MSG_HEADER_SIZE);
void * event_data = &ev_msg->event_data[0];
Boolean handled = TRUE;
char ifr_name[IFNAMSIZ];
switch (ev_msg->kev_subclass) {
case KEV_INET_SUBCLASS : {
switch (ev_msg->event_code) {
case KEV_INET_NEW_ADDR :
case KEV_INET_CHANGED_ADDR :
case KEV_INET_ADDR_DELETED :
case KEV_INET_SIFDSTADDR :
case KEV_INET_SIFBRDADDR :
case KEV_INET_SIFNETMASK : {
struct kev_in_data * ev;
ev = (struct kev_in_data *)event_data;
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(&ev->link_data, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process IPv4 address change: %s: %d", (char *)ifr_name, ev_msg->event_code);
ipv4_interface_update(NULL, ifr_name);
if (ev_msg->event_code
!= KEV_INET_ADDR_DELETED) {
check_interface_link_status(ifr_name);
}
break;
}
case KEV_INET_ARPCOLLISION : {
struct kev_in_collision * ev;
ev = (struct kev_in_collision *)event_data;
if ((dataLen < sizeof(*ev))
|| (dataLen < (sizeof(*ev) + ev->hw_len))) {
handled = FALSE;
break;
}
copy_if_name(&ev->link_data, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process ARP collision: %s", (char *)ifr_name);
ipv4_arp_collision(ifr_name,
ev->ia_ipaddr,
ev->hw_len,
ev->hw_addr);
break;
}
#if !TARGET_OS_IPHONE
case KEV_INET_PORTINUSE : {
struct kev_in_portinuse * ev;
ev = (struct kev_in_portinuse *)event_data;
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
SC_log(LOG_INFO, "Process port-in-use: %hu, %u", ev->port, ev->req_pid);
ipv4_port_in_use(ev->port, ev->req_pid);
break;
}
#endif
case KEV_INET_ARPRTRFAILURE: {
const struct kev_in_arpfailure * ev;
ev = (const struct kev_in_arpfailure *)event_data;
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(&ev->link_data, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process router ARP failure: %s", (char *)ifr_name);
ipv4_router_arp_failure(ifr_name);
break;
}
case KEV_INET_ARPRTRALIVE: {
const struct kev_in_arpalive * ev;
ev = (const struct kev_in_arpalive *)event_data;
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(&ev->link_data, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process router ARP alive: %s", (char *)ifr_name);
ipv4_router_arp_alive(ifr_name);
break;
}
default :
break;
}
break;
}
case KEV_INET6_SUBCLASS : {
struct kev_in6_data * ev;
ev = (struct kev_in6_data *)event_data;
switch (ev_msg->event_code) {
case KEV_INET6_NEW_USER_ADDR :
case KEV_INET6_CHANGED_ADDR :
case KEV_INET6_ADDR_DELETED :
case KEV_INET6_NEW_LL_ADDR :
case KEV_INET6_NEW_RTADV_ADDR :
case KEV_INET6_DEFROUTER :
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(&ev->link_data, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process IPv6 address change: %s: %d", (char *)ifr_name, ev_msg->event_code);
interface_update_ipv6(NULL, ifr_name);
if (ev_msg->event_code == KEV_INET6_NEW_USER_ADDR
&& (ev->ia6_flags & IN6_IFF_DUPLICATED) != 0) {
ipv6_duplicated_address(ifr_name,
&ev->ia_addr.sin6_addr,
ETHER_ADDR_LEN,
&ev->ia_mac);
}
if (ev_msg->event_code
!= KEV_INET6_ADDR_DELETED) {
check_interface_link_status(ifr_name);
}
break;
default :
break;
}
break;
}
case KEV_DL_SUBCLASS : {
struct net_event_data * ev;
ev = (struct net_event_data *)event_data;
switch (ev_msg->event_code) {
case KEV_DL_IF_ATTACHED :
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(ev, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process interface attach: %s", (char *)ifr_name);
link_add(ifr_name);
break;
case KEV_DL_IF_DETACHED :
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(ev, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process interface detach: %s", (char *)ifr_name);
link_remove(ifr_name);
break;
case KEV_DL_IF_DETACHING :
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(ev, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process interface detaching: %s", (char *)ifr_name);
interface_detaching(ifr_name);
break;
case KEV_DL_PROTO_ATTACHED :
case KEV_DL_PROTO_DETACHED : {
struct kev_dl_proto_data * protoEvent;
protoEvent = (struct kev_dl_proto_data *)event_data;
if (dataLen < sizeof(*protoEvent)) {
handled = FALSE;
break;
}
copy_if_name(&protoEvent->link_data,
ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process protocol %s: %s (n=%d)",
(ev_msg->event_code == KEV_DL_PROTO_ATTACHED) ? "attach" : "detach",
(char *)ifr_name,
protoEvent->proto_remaining_count);
if (protoEvent->proto_remaining_count == 0) {
mark_if_down(ifr_name);
} else {
mark_if_up(ifr_name);
}
break;
}
#ifdef KEV_DL_IF_IDLE_ROUTE_REFCNT
case KEV_DL_IF_IDLE_ROUTE_REFCNT: {
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(ev, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process interface idle: %s", (char *)ifr_name);
interface_update_idle_state(ifr_name);
break;
}
#endif // KEV_DL_IF_IDLE_ROUTE_REFCNT
case KEV_DL_LINK_OFF :
case KEV_DL_LINK_ON :
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(ev, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process interface link %s: %s",
(ev_msg->event_code == KEV_DL_LINK_ON) ? "up" : "down",
(char *)ifr_name);
link_update_status(ifr_name, FALSE, FALSE);
break;
#ifdef KEV_DL_LINK_QUALITY_METRIC_CHANGED
case KEV_DL_LINK_QUALITY_METRIC_CHANGED: {
struct kev_dl_link_quality_metric_data * lqm_data;
lqm_data = (struct kev_dl_link_quality_metric_data *) event_data;
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(ev, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process interface quality: %s (q=%d)",
(char *)ifr_name,
lqm_data->link_quality_metric);
interface_update_quality_metric(ifr_name,
lqm_data->link_quality_metric);
break;
}
#endif // KEV_DL_LINK_QUALITY_METRIC_CHANGED
#ifdef KEV_DL_ISSUES
case KEV_DL_ISSUES: {
struct kev_dl_issues *issues;
issues = (struct kev_dl_issues *)event_data;
if (dataLen < sizeof(*ev)) {
handled = FALSE;
break;
}
copy_if_name(ev, ifr_name, sizeof(ifr_name));
SC_log(LOG_INFO, "Process interface link issues: %s",
(char *)ifr_name);
interface_update_link_issues(ifr_name,
issues->timestamp,
issues->modid,
DLIL_MODIDLEN,
issues->info,
(bcmp(issues->info, info_zero, DLIL_MODARGLEN) != 0)
? DLIL_MODARGLEN
: 0);
break;
}
#endif // KEV_DL_ISSUES
default :
break;
}
break;
}
#ifdef KEV_NETPOLICY_SUBCLASS
case KEV_NETPOLICY_SUBCLASS : {
break;
}
#endif // KEV_NETPOLICY_SUBCLASS
#ifdef KEV_SOCKET_SUBCLASS
case KEV_SOCKET_SUBCLASS : {
break;
}
#endif // KEV_SOCKET_SUBCLASS
#ifdef KEV_ND6_SUBCLASS
case KEV_ND6_SUBCLASS : {
break;
}
#endif // KEV_ND6_SUBCLASS
#ifdef KEV_NECP_SUBCLASS
case KEV_NECP_SUBCLASS : {
break;
}
#endif // KEV_NECP_SUBCLASS
#ifdef KEV_NETAGENT_SUBCLASS
case KEV_NETAGENT_SUBCLASS : {
break;
}
#endif // KEV_NETAGENT_SUBCLASS
#ifdef KEV_LOG_SUBCLASS
case KEV_LOG_SUBCLASS : {
break;
}
#endif // KEV_LOG_SUBCLASS
#ifdef KEV_NETEVENT_SUBCLASS
case KEV_NETEVENT_SUBCLASS : {
break;
}
#endif // KEV_NETEVENT_SUBCLASS
default :
break;
}
if (!handled) {
logEvent(CFSTR("Error processing (Apple network subclass)"), ev_msg);
}
return;
}
static Boolean
eventCallback(int so)
{
ssize_t status;
union {
char bytes[1024];
struct kern_event_msg ev_msg1; } buf;
struct kern_event_msg *ev_msg = &buf.ev_msg1;
ssize_t offset = 0;
status = recv(so, &buf, sizeof(buf), 0);
if (status == -1) {
SC_log(LOG_NOTICE, "recv() failed: %s", strerror(errno));
return FALSE;
}
cache_open();
while (offset < status) {
if ((offset + ev_msg->total_size) > status) {
SC_log(LOG_NOTICE, "missed SYSPROTO_EVENT event, buffer not big enough");
break;
}
switch (ev_msg->vendor_code) {
case KEV_VENDOR_APPLE :
switch (ev_msg->kev_class) {
case KEV_NETWORK_CLASS :
processEvent_Apple_Network(ev_msg);
break;
default :
break;
}
break;
default :
break;
}
offset += ev_msg->total_size;
ev_msg = (struct kern_event_msg *)(void *)&buf.bytes[offset];
}
cache_write(store);
cache_close();
post_network_changed();
messages_post();
return TRUE;
}
__private_extern__ void
config_new_interface(const char * ifname)
{
xpc_object_t if_list;
if (ifname == NULL) {
network_config_check_interface_settings(NULL);
return;
}
if_list = xpc_array_create(NULL, 0);
xpc_array_set_string(if_list, XPC_ARRAY_APPEND, ifname);
network_config_check_interface_settings(if_list);
xpc_release(if_list);
return;
}
static void
update_interfaces(const char * msg, Boolean first_time)
{
Boolean added = FALSE;
struct ifaddrs * ifap = NULL;
CFMutableArrayRef ifList = NULL;
struct ifaddrs * scan;
if (getifaddrs(&ifap) == -1) {
messages_add_msg_with_arg("getifaddrs", strerror(errno));
SC_log(LOG_NOTICE, "getifaddrs() failed: %s", strerror(errno));
goto done;
}
ifList = interfaceListCopy();
for (scan = ifap; scan != NULL; scan = scan->ifa_next) {
if (scan->ifa_addr == NULL
|| scan->ifa_addr->sa_family != AF_LINK) {
continue;
}
if (interfaceListAddInterface(ifList, scan->ifa_name)) {
messages_add_msg_with_arg(msg, scan->ifa_name);
added = TRUE;
if (!first_time) {
config_new_interface(scan->ifa_name);
}
}
}
if (added) {
interfaceListUpdate(ifList);
}
CFRelease(ifList);
if (first_time) {
ipv4_interface_update(ifap, NULL);
interface_update_ipv6(ifap, NULL);
}
freeifaddrs(ifap);
done:
if (first_time) {
config_new_interface(NULL);
}
return;
}
#define TIMER_INTERVAL (6LL * NSEC_PER_SEC)
#define MAX_TIMER_COUNT 20
static void
check_for_new_interfaces(void * context);
static void
schedule_timer(void)
{
dispatch_after_f(dispatch_time(DISPATCH_TIME_NOW, TIMER_INTERVAL),
S_kev_queue,
NULL,
check_for_new_interfaces);
return;
}
static void
check_for_new_interfaces(void * context)
{
static int count;
char msg[32];
count++;
snprintf(msg, sizeof(msg), "update %d (of %d)", count, MAX_TIMER_COUNT);
cache_open();
update_interfaces(msg, FALSE);
cache_write(store);
cache_close();
messages_post();
if (count < MAX_TIMER_COUNT) {
schedule_timer();
}
else {
messages_free();
}
return;
}
static void
prime(void)
{
SC_log(LOG_DEBUG, "prime() called");
cache_open();
messages_init();
update_interfaces("prime", TRUE);
cache_write(store);
cache_close();
network_changed = TRUE;
post_network_changed();
messages_post();
dispatch_resume(S_kev_source);
schedule_timer();
return;
}
__private_extern__
void
prime_KernelEventMonitor()
{
dispatch_async(S_kev_queue, ^{ prime(); });
return;
}
static Boolean
initialize_store(void)
{
store = SCDynamicStoreCreate(NULL,
CFSTR("Kernel Event Monitor plug-in"),
NULL,
NULL);
if (store == NULL) {
SC_log(LOG_ERR, "SCDynamicStoreCreate() failed: %s", SCErrorString(SCError()));
return (FALSE);
}
return (TRUE);
}
__private_extern__
void
load_KernelEventMonitor(CFBundleRef bundle, Boolean bundleVerbose)
{
struct kev_request kev_req;
int so;
int status;
if (bundleVerbose) {
_verbose = TRUE;
}
SC_log(LOG_DEBUG, "load() called");
SC_log(LOG_DEBUG, " bundle ID = %@", CFBundleGetIdentifier(bundle));
if (!initialize_store()) {
SC_log(LOG_ERR, "kernel event monitor disabled");
return;
}
so = socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT);
if (so != -1) {
kev_req.vendor_code = KEV_VENDOR_APPLE;
kev_req.kev_class = KEV_NETWORK_CLASS;
kev_req.kev_subclass = KEV_ANY_SUBCLASS;
status = ioctl(so, SIOCSKEVFILT, &kev_req);
if (status != 0) {
SC_log(LOG_ERR, "could not establish event filter, ioctl() failed: %s", strerror(errno));
(void) close(so);
so = -1;
}
} else {
SC_log(LOG_ERR, "could not open event socket, socket() failed: %s", strerror(errno));
}
if (so != -1) {
int yes = 1;
status = ioctl(so, FIONBIO, &yes);
if (status) {
SC_log(LOG_ERR, "could not set non-blocking io, ioctl() failed: %s", strerror(errno));
(void) close(so);
so = -1;
}
}
if (so == -1) {
SC_log(LOG_ERR, "kernel event monitor disabled");
CFRelease(store);
return;
}
S_kev_queue = dispatch_queue_create("com.apple.SystemConfiguration.KernelEventMonitor", NULL);
S_kev_source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, so, 0, S_kev_queue);
dispatch_source_set_cancel_handler(S_kev_source, ^{
close(so);
});
dispatch_source_set_event_handler(S_kev_source, ^{
os_activity_t activity;
Boolean ok;
activity = os_activity_create("processing network kernel events",
OS_ACTIVITY_CURRENT,
OS_ACTIVITY_FLAG_DEFAULT);
os_activity_scope(activity);
ok = eventCallback(so);
if (!ok) {
SC_log(LOG_ERR, "kernel event monitor disabled");
dispatch_source_cancel(S_kev_source);
}
os_release(activity);
});
return;
}
#ifdef MAIN
#include "ev_dlil.c"
#define appendAddress appendAddress_v4
#define getIF getIF_v4
#define updateStore updateStore_v4
#include "ev_ipv4.c"
#undef appendAddress
#undef getIF
#undef updateStore
#define appendAddress appendAddress_v6
#define getIF getIF_v6
#define updateStore updateStore_v6
#include "ev_ipv6.c"
#undef appendAddress
#undef getIF
#undef updateStore
int
main(int argc, char **argv)
{
_sc_log = FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
load_KernelEventMonitor(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
prime_KernelEventMonitor();
dispatch_main();
exit(0);
return 0;
}
#endif