#include <sys/param.h>
#include <sys/proc.h>
#include <sys/kpi_mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <string.h> // For bzero
#include <libkern/libkern.h> // for printf
#include <kern/debug.h> // For panic
#include <net/if.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>
#include <netinet/tcp.h>
#include <netinet/tcp_fsm.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <libkern/OSMalloc.h>
#include <libkern/OSAtomic.h>
#include <kern/thread_call.h>
#include "ip_edgehole.h"
enum
{
kEdgeHoleFlag_BlockInternet = 0x00000001,
kEdgeHoleFlag_BlockVV = 0x00000002
};
struct edgehole_tag
{
u_int32_t eh_flags;
struct inpcbinfo *eh_inpinfo;
struct inpcb *eh_inp;
};
struct edgehole_delayed_notify
{
struct edgehole_delayed_notify *next;
struct inpcbinfo *inpinfo;
struct inpcb *inp;
};
static mbuf_tag_id_t edgehole_tag = 0;
static thread_call_t edgehole_callout = NULL;
static OSMallocTag edgehole_mtag = 0;
static struct edgehole_delayed_notify *edgehole_delay_list = NULL;
#ifndef HAS_COMPARE_AND_SWAP_PTR
static Boolean
OSCompareAndSwapPtr(
void *oldValue,
void *newValue,
volatile void *address)
{
return OSCompareAndSwap((UInt32)oldValue, (UInt32)newValue, (volatile UInt32*)address);
}
#endif
static void
ip_edgehole_notify_delayed(
struct inpcb *inp,
struct inpcbinfo *inpinfo)
{
if (in_pcb_checkstate(inp, WNT_ACQUIRE, 0) != WNT_STOPUSING)
{
struct socket *so = inp->inp_socket;
if (so && so != &inpinfo->nat_dummy_socket)
{
socket_lock(so, 1);
if (in_pcb_checkstate(inp, WNT_RELEASE,1) != WNT_STOPUSING)
{
if (inp->inp_ip_p == IPPROTO_TCP)
{
union
{
caddr_t caddrt_sucks;
void *void_ptr;
} bite_me;
bite_me.caddrt_sucks = inp->inp_ppcb;
tcp_drop((struct tcpcb*)bite_me.void_ptr, EPERM);
}
else
{
socantsendmore(so);
}
}
socket_unlock(so, 1);
}
}
}
static void
ip_edgehole_process_delayed(
__unused void *unused1,
__unused void *unused2)
{
struct edgehole_delayed_notify *head;
while (edgehole_delay_list)
{
do
{
head = edgehole_delay_list;
}
while (!OSCompareAndSwapPtr(head, NULL, &edgehole_delay_list));
if (head == NULL)
{
break;
}
struct edgehole_delayed_notify *current;
struct edgehole_delayed_notify **current_p;
struct edgehole_delayed_notify *ye_dead;
for (current = head; current && current->next; current = current->next)
{
current_p = &head;
while (*current_p)
{
if ((*current_p)->inp == current->inp)
{
ye_dead = *current_p;
*current_p = ye_dead->next;
OSFree(ye_dead, sizeof(*ye_dead), edgehole_mtag);
}
else
{
current_p = &(*current_p)->next;
}
}
}
while (head)
{
struct inpcbinfo *lockedinfo;
lockedinfo = head->inpinfo;
lck_rw_lock_shared(lockedinfo->mtx);
struct inpcb *inp;
LIST_FOREACH(inp, lockedinfo->listhead, inp_list)
{
for (current = head; current != NULL; current = current->next)
{
if (current->inpinfo == lockedinfo && current->inp == inp)
{
ip_edgehole_notify_delayed(inp, lockedinfo);
}
}
}
lck_rw_done(lockedinfo->mtx);
current_p = &head;
while (*current_p)
{
if ((*current_p)->inpinfo == lockedinfo)
{
ye_dead = *current_p;
*current_p = ye_dead->next;
OSFree(ye_dead, sizeof(*ye_dead), edgehole_mtag);
}
else
{
current_p = &(*current_p)->next;
}
}
}
}
}
static void
ip_edgehole_notify(
struct edgehole_tag *tag)
{
if (tag->eh_inp == NULL || tag->eh_inpinfo == NULL)
return;
struct edgehole_delayed_notify *delayed = OSMalloc(sizeof(*delayed), edgehole_mtag);
if (delayed)
{
delayed->inp = tag->eh_inp;
delayed->inpinfo = tag->eh_inpinfo;
do
{
delayed->next = edgehole_delay_list;
}
while (!OSCompareAndSwapPtr(delayed->next, delayed, &edgehole_delay_list));
thread_call_enter(edgehole_callout);
}
}
__private_extern__ void
ip_edgehole_attach(
struct inpcb *inp)
{
inp->inpcb_edgehole_flags = 0;
inp->inpcb_edgehole_mask = 0;
#ifdef TEST_THE_EVIL_EDGE_HOLE
char pidname[64];
proc_selfname(pidname, sizeof(pidname));
pidname[sizeof(pidname) -1] = 0;
if (strcmp(pidname, "MobileSafari") == 0 ||
strcmp(pidname, "ping") == 0)
{
inp->inpcb_edgehole_flags = kEdgeHoleFlag_BlockInternet;
inp->inpcb_edgehole_mask = kEdgeHoleFlag_BlockInternet;
}
#endif
if (inp->inpcb_edgehole_mask != 0)
{
if (edgehole_callout == NULL)
{
thread_call_t tmp_callout = thread_call_allocate(ip_edgehole_process_delayed, NULL);
if (!tmp_callout) panic("ip_edgehole_attach: thread_call_allocate failed");
if (!OSCompareAndSwapPtr(NULL, tmp_callout, &edgehole_callout))
thread_call_free(tmp_callout);
}
if (edgehole_mtag == 0)
{
OSMallocTag mtag = OSMalloc_Tagalloc("com.apple.ip_edgehole", 0);
if (!mtag) panic("ip_edgehole_attach: OSMalloc_Tagalloc failed");
if (!OSCompareAndSwapPtr(NULL, mtag, &edgehole_mtag))
OSMalloc_Tagfree(mtag);
}
}
}
__private_extern__ void
ip_edgehole_mbuf_tag(
struct inpcb *inp,
mbuf_t m)
{
if (inp->inpcb_edgehole_mask == 0)
{
return;
}
if (edgehole_tag == 0)
mbuf_tag_id_find("com.apple.edgehole", &edgehole_tag);
struct edgehole_tag *tag;
size_t length;
if (mbuf_tag_find(m, edgehole_tag, 0, &length, (void**)&tag) == 0)
{
if (length != sizeof(*tag))
panic("ip_edgehole_mbuf_tag - existing tag is wrong size");
tag->eh_flags = (tag->eh_flags & (~inp->inpcb_edgehole_mask)) |
(inp->inpcb_edgehole_flags & inp->inpcb_edgehole_mask);
}
else if ((inp->inpcb_edgehole_mask & inp->inpcb_edgehole_flags) != 0)
{
if (mbuf_tag_allocate(m, edgehole_tag, 0, sizeof(*tag), MBUF_WAITOK, (void**)&tag) != 0)
panic("ip_edgehole_mbuf_tag - mbuf_tag_allocate failed");
tag->eh_flags = (inp->inpcb_edgehole_flags & inp->inpcb_edgehole_mask);
tag->eh_inp = inp;
tag->eh_inpinfo = inp->inp_pcbinfo;
}
}
int
ip_edgehole_filter(
mbuf_t *m,
__unused int isVV)
{
struct edgehole_tag *tag;
size_t length;
if (mbuf_tag_find(*m, edgehole_tag, 0, &length, (void**)&tag) == 0)
{
if (length != sizeof(*tag))
panic("ip_edgehole_filter - existing tag is wrong size");
if ((tag->eh_flags & kEdgeHoleFlag_BlockInternet) != 0)
{
ip_edgehole_notify(tag);
mbuf_freem(*m); *m = NULL;
return EPERM;
}
}
return 0;
}