#import <stdlib.h>
#import <unistd.h>
#import <string.h>
#import <stdio.h>
#import <sys/types.h>
#import <sys/wait.h>
#import <sys/errno.h>
#import <sys/socket.h>
#import <sys/ioctl.h>
#import <sys/time.h>
#import <sys/sockio.h>
#import <net/if.h>
#import <net/etherdefs.h>
#import <netinet/in.h>
#import <netinet/udp.h>
#import <netinet/in_systm.h>
#import <netinet/ip.h>
#import <net/if_arp.h>
#import <net/etherdefs.h>
#import <netinet/if_ether.h>
#import <arpa/inet.h>
#import <net/if_types.h>
#import <net/bpf.h>
#import "util.h"
#import <syslog.h>
#import "bpflib.h"
#import "util.h"
#import "dprintf.h"
#import "dynarray.h"
#import "timer.h"
#import "FDSet.h"
#import "ipconfigd_globals.h"
#import "arp_session.h"
extern char * ether_ntoa(struct ether_addr *e);
extern struct ether_addr * ether_aton(char *);
#ifdef TEST_ARP_SESSION
#define my_log syslog
#endif TEST_ARP_SESSION
static int
arp_open_fd(arp_session_t * session, arp_client_t * client);
static void
arp_close_fd(arp_session_t * session);
static void
arp_start(arp_session_t * session);
static void
arp_retransmit(void * arg1, void * arg2, void * arg3);
static void
arp_read(void * arg1, void * arg2);
static boolean_t
arp_is_our_address(interface_t * if_p,
int hwtype, void * hwaddr, int hwlen);
static __inline__ char *
arpop_name(u_int16_t op)
{
switch (op) {
case ARPOP_REQUEST:
return "ARP REQUEST";
case ARPOP_REPLY:
return "ARP REPLY";
case ARPOP_REVREQUEST:
return "REVARP REQUEST";
case ARPOP_REVREPLY:
return "REVARP REPLY";
default:
break;
}
return ("<unknown>");
}
static void
dump_arp(struct ether_arp * earp)
{
printf("\n");
printf("%s type=0x%x proto=0x%x\n", arpop_name(ntohs(earp->ea_hdr.ar_op)),
ntohs(earp->ea_hdr.ar_hrd), ntohs(earp->ea_hdr.ar_pro));
if (earp->ea_hdr.ar_hln == sizeof(earp->arp_sha)) {
printf("Sender H/W\t%s\n",
ether_ntoa((struct ether_addr *)earp->arp_sha));
printf("Target H/W\t%s\n",
ether_ntoa((struct ether_addr *)earp->arp_tha));
}
printf("Sender IP\t%s\n", inet_ntoa(*((struct in_addr *)earp->arp_spa)));
printf("Target IP\t%s\n", inet_ntoa(*((struct in_addr *)earp->arp_tpa)));
return;
}
static struct ether_addr ether_broadcast = {
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
};
#ifdef TEST_ARP_SESSION
static FDSet_t * G_readers = NULL;
#endif TEST_ARP_SESSION
static void
arp_close_fd(arp_session_t * session)
{
if (session->read_callout) {
FDSet_remove_callout(G_readers, &session->read_callout);
}
session->bpf_fd = -1;
if (session->receive_buf) {
free(session->receive_buf);
session->receive_buf = NULL;
}
return;
}
static void
arp_error(void * arg1, void * arg2, void * arg3)
{
void * c_arg1;
void * c_arg2;
arp_client_t * client;
arp_result_func_t * func;
arp_session_t * session = (arp_session_t *)arg1;
arp_result_t result;
client = session->pending_client;
if (client == NULL) {
return;
}
c_arg1 = client->arg1;
c_arg2 = client->arg2;
func = client->func;
bzero(&result, sizeof(result));
result.ip_address = client->target_ip;
result.error = TRUE;
session->pending_client = NULL;
client->func = client->arg1 = client->arg2 = NULL;
arp_start(session);
(*func)(c_arg1, c_arg2, &result);
return;
}
static boolean_t
arp_is_our_address(interface_t * if_p,
int hwtype, void * hwaddr, int hwlen)
{
if (hwtype == if_link_arptype(if_p)
&& hwlen == if_link_length(if_p)
&& bcmp(hwaddr, if_link_address(if_p), hwlen) == 0) {
return (TRUE);
}
return (FALSE);
}
static void
arp_read(void * arg1, void * arg2)
{
arp_client_t * client;
int n;
char * offset;
arp_session_t * session = (arp_session_t *)arg1;
client = session->pending_client;
if (client == NULL) {
my_log(LOG_ERR, "arp_read: no pending clients?");
arp_close_fd(session);
arp_start(session);
return;
}
n = read(session->bpf_fd, session->receive_buf, session->receive_bufsize);
if (n < 0) {
my_log(LOG_ERR, "arp_read: read(%s) failed, %s (%d)",
if_name, strerror(errno), errno);
sprintf(client->errmsg, "arp_read: read(%s) failed, %s (%d)",
if_name(client->if_p), strerror(errno), errno);
goto failed;
}
for (offset = session->receive_buf; n > 0; ) {
void * c_arg1;
void * c_arg2;
struct bpf_hdr * bpf = (struct bpf_hdr *)offset;
struct ether_arp * earp;
arp_result_func_t * func;
char * pkt_start;
arp_result_t result;
dprintf(("bpf remaining %d header %d captured %d\n", n,
bpf->bh_hdrlen, bpf->bh_caplen));
pkt_start = offset + bpf->bh_hdrlen;
earp = (struct ether_arp *)(pkt_start + sizeof(struct ether_header));
if (session->debug) {
dump_arp(earp);
}
if (ntohs(earp->ea_hdr.ar_op) != ARPOP_REPLY
|| ntohs(earp->ea_hdr.ar_hrd) != ARPHRD_ETHER
|| ntohs(earp->ea_hdr.ar_pro) != ETHERTYPE_IP
|| (client->target_ip.s_addr
!= ((struct in_addr *)earp->arp_spa)->s_addr)
|| bcmp(earp->arp_tha, if_link_address(client->if_p),
sizeof(earp->arp_tha))
|| (client->sender_ip.s_addr
!= ((struct in_addr *)earp->arp_tpa)->s_addr)
|| (*session->is_our_address)(client->if_p, earp->ea_hdr.ar_hrd,
earp->arp_sha,
earp->ea_hdr.ar_hln) == TRUE) {
int skip = bpf->bh_caplen + bpf->bh_hdrlen;
if (skip == 0) {
break;
}
offset += skip;
n -= skip;
continue;
}
timer_cancel(session->timer_callout);
func = client->func;
c_arg1 = client->arg1;
c_arg2 = client->arg2;
client->func = client->arg1 = client->arg2 = NULL;
session->pending_client = NULL;
bzero(&result, sizeof(result));
result.in_use = TRUE;
result.ip_address = client->target_ip;
result.hwtype = ntohs(earp->ea_hdr.ar_hrd);
result.hwlen = earp->ea_hdr.ar_hln;
result.hwaddr = earp->arp_sha;
(*func)(c_arg1, c_arg2, &result);
arp_start(session);
break;
}
return;
failed:
{
struct timeval t;
t.tv_sec = 0;
t.tv_usec = 100;
timer_set_relative(session->timer_callout, t,
(timer_func_t *)arp_error,
session, NULL, NULL);
}
return;
}
static int
arp_open_fd(arp_session_t * session, arp_client_t * client)
{
int status;
if (session->bpf_fd < 0) {
session->bpf_fd = bpf_new();
if (session->bpf_fd < 0) {
my_log(LOG_ERR, "arp_open_fd: bpf_new(%s) failed, %s (%d)",
if_name(client->if_p), strerror(errno), errno);
sprintf(client->errmsg, "arp_open_fd: bpf_new(%s) failed, %s (%d)",
if_name(client->if_p), strerror(errno), errno);
goto failed;
}
bpf_set_immediate(session->bpf_fd, 1);
status = bpf_arp_filter(session->bpf_fd);
if (status < 0) {
my_log(LOG_ERR, "arp_open_fd: bpf_arp_filter(%s) failed: %s (%d)",
if_name(client->if_p), strerror(errno), errno);
sprintf(client->errmsg,
"arp_open_fd: bpf_arp_filter(%s) failed: %s (%d)",
if_name(client->if_p), strerror(errno), errno);
goto failed;
}
status = bpf_get_blen(session->bpf_fd, &session->receive_bufsize);
if (status < 0) {
my_log(LOG_ERR,
"arp_open_fd: bpf_get_blen(%s) failed, %s (%d)",
if_name(client->if_p), strerror(errno), errno);
sprintf(client->errmsg,
"arp_open_fd: bpf_get_blen(%s) failed, %s (%d)",
if_name(client->if_p), strerror(errno), errno);
goto failed;
}
session->receive_buf = malloc(session->receive_bufsize);
if (session->receive_buf == NULL) {
my_log(LOG_ERR,
"arp_open_fd: malloc failed");
sprintf(client->errmsg, "arp_open_fd: malloc failed");
goto failed;
}
session->read_callout
= FDSet_add_callout(G_readers, session->bpf_fd,
arp_read, session, NULL);
}
status = bpf_setif(session->bpf_fd, if_name(client->if_p));
if (status < 0) {
my_log(LOG_ERR, "arp_open_fd: bpf_setif(%s) failed: %s (%d)",
if_name, strerror(errno), errno);
sprintf(client->errmsg, "arp_open_fd: bpf_setif(%s) failed: %s (%d)",
if_name(client->if_p), strerror(errno), errno);
goto failed;
}
return (1);
failed:
arp_close_fd(session);
return (0);
}
static int
arp_transmit(arp_session_t * session, arp_client_t * client)
{
struct ether_header*eh_p;
struct ether_arp * earp;
struct arphdr * hdr;
int status = 0;
char txbuf[128];
if (!arp_open_fd(session, client))
return (0);
bzero(txbuf, sizeof(txbuf));
eh_p = (struct ether_header *)txbuf;
bcopy(ðer_broadcast, eh_p->ether_dhost,
sizeof(eh_p->ether_dhost));
eh_p->ether_type = htons(ETHERTYPE_ARP);
earp = (struct ether_arp *)(txbuf + sizeof(*eh_p));
hdr = &earp->ea_hdr;
hdr->ar_hrd = htons(ARPHRD_ETHER);
hdr->ar_pro = htons(ETHERTYPE_IP);
hdr->ar_hln = sizeof(earp->arp_sha);;
hdr->ar_pln = sizeof(struct in_addr);
hdr->ar_op = htons(ARPOP_REQUEST);
bcopy(if_link_address(client->if_p), earp->arp_sha,
sizeof(earp->arp_sha));
*((struct in_addr *)earp->arp_spa) = client->sender_ip;
*((struct in_addr *)earp->arp_tpa) = client->target_ip;
status = bpf_write(session->bpf_fd, txbuf, sizeof(*eh_p) + sizeof(*earp));
if (status < 0) {
my_log(LOG_ERR, "arp_transmit(%s) failed, %s (%d)",
if_name, strerror(errno), errno);
sprintf(client->errmsg, "arp_transmit(%s) failed, %s (%d)",
if_name(client->if_p), strerror(errno), errno);
return (0);
}
return (1);
}
static void
arp_retransmit(void * arg1, void * arg2, void * arg3)
{
arp_client_t * client;
arp_session_t * session = (arp_session_t *)arg1;
client = session->pending_client;
if (client == NULL)
return;
if (session->pending_client_try == ARP_PROBE_TRIES) {
void * c_arg1 = client->arg1;
void * c_arg2 = client->arg2;
arp_result_func_t * func = client->func;
arp_result_t result;
client->func = client->arg1 = client->arg2 = NULL;
session->pending_client = NULL;
arp_start(session);
bzero(&result, sizeof(result));
result.ip_address = client->target_ip;
(*func)(c_arg1, c_arg2, &result);
return;
}
session->pending_client_try++;
if (arp_transmit(session, client)) {
struct timeval t;
t.tv_sec = ARP_PROBE_TRY_INTERVAL_SECS;
t.tv_usec = ARP_PROBE_TRY_INTERVAL_USECS;
timer_set_relative(session->timer_callout, t,
(timer_func_t *)arp_retransmit,
session, NULL, NULL);
}
else {
struct timeval t;
t.tv_sec = 0;
t.tv_usec = 100;
timer_set_relative(session->timer_callout, t,
(timer_func_t *)arp_error,
session, NULL, NULL);
}
return;
}
static void
arp_start(arp_session_t * session)
{
arp_client_t * next_client = NULL;
int i;
if (session->pending_client)
return;
for (i = 0; i < dynarray_count(&session->clients); i++) {
arp_client_t * client;
client = dynarray_element(&session->clients, i);
if (client->func) {
next_client = client;
break;
}
}
if (next_client == NULL) {
arp_close_fd(session);
timer_cancel(session->timer_callout);
return;
}
session->pending_client = next_client;
session->pending_client_try = 0;
arp_retransmit(session, NULL, NULL);
return;
}
arp_client_t *
arp_client_init(arp_session_t * session, interface_t * if_p)
{
arp_client_t * client;
if (if_link_arptype(if_p) != ARPHRD_ETHER) {
my_log(LOG_DEBUG, "arp_client_init(%s): not ethernet",
if_name(if_p));
return (NULL);
}
client = malloc(sizeof(*client));
if (client == NULL) {
return (NULL);
}
bzero(client, sizeof(*client));
client->if_p = if_p;
if (dynarray_add(&session->clients, client) == FALSE) {
free(client);
return (NULL);
}
client->session = session;
return (client);
}
void
arp_client_free(arp_client_t * * client_p)
{
arp_session_t * session;
arp_client_t * client = *client_p;
int i;
if (client == NULL)
return;
session = client->session;
arp_cancel_probe(client);
i = dynarray_index(&session->clients, client);
if (i == -1) {
return;
}
dynarray_remove(&session->clients, i, NULL);
free(client);
*client_p = NULL;
return;
}
void
arp_probe(arp_client_t * client,
arp_result_func_t * func, void * arg1, void * arg2,
struct in_addr sender_ip, struct in_addr target_ip)
{
arp_session_t * session = client->session;
client->sender_ip = sender_ip;
client->target_ip = target_ip;
client->func = func;
client->arg1 = arg1;
client->arg2 = arg2;
client->errmsg[0] = '\0';
arp_start(session);
return;
}
char *
arp_client_errmsg(arp_client_t * client)
{
return (client->errmsg);
}
void
arp_cancel_probe(arp_client_t * client)
{
arp_session_t * session = client->session;
client->errmsg[0] = '\0';
client->func = client->arg1 = client->arg2 = NULL;
if (client == session->pending_client) {
session->pending_client = NULL;
arp_start(session);
}
return;
}
arp_session_t *
arp_session_init(arp_our_address_func_t * func)
{
arp_session_t * session;
timer_callout_t * timer;
timer = timer_callout_init();
if (timer == NULL)
return (NULL);
session = malloc(sizeof(*session));
if (session == NULL) {
timer_callout_free(&timer);
return (NULL);
}
bzero(session, sizeof(*session));
dynarray_init(&session->clients, free, NULL);
session->bpf_fd = -1;
session->timer_callout = timer;
if (func == NULL) {
session->is_our_address = arp_is_our_address;
}
else {
session->is_our_address = func;
}
return (session);
}
void
arp_session_free(arp_session_t * * session_p)
{
arp_session_t * session = *session_p;
dynarray_free(&session->clients);
arp_close_fd(session);
if (session->timer_callout) {
timer_callout_free(&session->timer_callout);
}
bzero(session, sizeof(*session));
free(session);
*session_p = NULL;
return;
}
void
arp_session_set_debug(arp_session_t * session, int debug)
{
session->debug = debug;
return;
}
#ifdef TEST_ARP_SESSION
void
arp_test(void * arg1, void * arg2, void * arg3)
{
arp_client_t * client = (arp_client_t *)arg1;
arp_result_t * result = (arp_result_t *)arg3;
if (result->error) {
printf("ARP probe failed: '%s'\n",
client->errmsg);
exit(1);
}
if (result->in_use) {
printf("ip address " IP_FORMAT " is in use by " EA_FORMAT "\n",
IP_LIST(&client->target_ip),
EA_LIST((char *)result->hwaddr));
}
else {
printf("ip address " IP_FORMAT " is not in use\n",
IP_LIST(&client->target_ip));
}
exit(0);
}
int
main(int argc, char * argv[])
{
interface_t * if_p;
interface_list_t * list_p;
arp_session_t * p;
arp_client_t * c;
struct in_addr sender_ip;
struct in_addr target_ip;
if (argc < 4) {
printf("usage: arpcheck <ifname> <sender ip> <target ip>\n");
exit(1);
}
list_p = ifl_init(FALSE);
if (list_p == NULL) {
fprintf(stderr, "couldn't get interface list\n");
exit(1);
}
if_p = ifl_find_name(list_p, argv[1]);
if (if_p == NULL) {
fprintf(stderr, "interface %s does not exist\n", argv[1]);
exit(1);
}
G_readers = FDSet_init();
if (G_readers == NULL) {
fprintf(stderr, "FDSet_init failed\n");
exit(1);
}
p = arp_session_init(NULL);
if (p == NULL) {
fprintf(stderr, "arp_session_init failed\n");
exit(1);
}
arp_session_set_debug(p, 1);
if (inet_aton(argv[2], &sender_ip) == 0) {
fprintf(stderr, "invalid ip address %s\n", argv[3]);
exit(1);
}
if (inet_aton(argv[3], &target_ip) == 0) {
fprintf(stderr, "invalid ip address %s\n", argv[4]);
exit(1);
}
c = arp_client_init(p, if_p);
if (c == NULL) {
fprintf(stderr, "arp_client_init %s\n", if_name(if_p));
exit(1);
}
arp_probe(c, arp_test, c, NULL, sender_ip, target_ip);
CFRunLoopRun();
exit(0);
}
#endif TEST_ARP_SESSION