blob: 64dab284b54f22fdb5ed1d04594238bc9bb63149 [file] [log] [blame] [raw]
/*
* Copyright (C) 2000-2009, Parallels, Inc. All rights reserved.
*
* This code is public domain.
*
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
* WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <linux/if_packet.h>
#include <linux/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <getopt.h>
#include <unistd.h>
#include <signal.h>
#define EXC_OK 0
#define EXC_USAGE 1
#define EXC_SYS 2
#define EXC_RECV 3
#define EXC_NORECV 4
/* log facility */
#define LOG_ERROR 0x01
#define LOG_WARNING 0x02
#define LOG_INFO 0x03
#define LOG_DEBUG 0x04
#define LOGGER_NAME "arpsend: "
int debug_level = LOG_INFO;
#define logger(level, fmt, ...) \
if((level) <= debug_level) fprintf(stderr, LOGGER_NAME fmt "\n",##__VA_ARGS__)
#define IP_ADDR_LEN 4
#define REQUEST ARPOP_REQUEST
#define REPLY ARPOP_REPLY
struct arp_packet {
u_char targ_hw_addr[ETH_ALEN]; /* ethernet destination MAC address */
u_char src_hw_addr[ETH_ALEN]; /* ethernet source MAC address */
u_short frame_type; /* ethernet packet type */
/* ARP packet */
u_short hw_type; /* hardware address type (ethernet) */
u_short prot_type; /* resolve protocol address type (ip) */
u_char hw_addr_size;
u_char prot_addr_size;
u_short op; /* subtype */
u_char sndr_hw_addr[ETH_ALEN];
u_char sndr_ip_addr[IP_ADDR_LEN];
u_char rcpt_hw_addr[ETH_ALEN];
u_char rcpt_ip_addr[IP_ADDR_LEN];
/* ethernet padding */
u_char padding[18];
};
enum {
AR_NOTHING = 0,
AR_UPDATE,
AR_DETECT,
AR_REQUEST,
AR_REPLY
} cmd = AR_NOTHING;
char* iface = NULL;
int timeout = 1;
int count = -1;
char *ip6_addr;
/* source MAC address in Ethernet header */
unsigned char src_hwaddr[ETH_ALEN];
int src_hwaddr_flag = 0;
/* target MAC address in Ethernet header */
unsigned char trg_hwaddr[ETH_ALEN];
int trg_hwaddr_flag = 0;
/* source MAC address in ARP header */
unsigned char src_arp_hwaddr[ETH_ALEN];
int src_arp_hwaddr_flag = 0;
/* target MAC address in ARP header */
unsigned char trg_arp_hwaddr[ETH_ALEN];
int trg_arp_hwaddr_flag = 0;
struct in_addr src_ipaddr;
int src_ipaddr_flag = 0;
int at_once = 0;
#define MAX_IPADDR_NUMBER 1024
struct in_addr trg_ipaddr[MAX_IPADDR_NUMBER];
int trg_ipaddr_count = 0;
int trg_ipaddr_flag = 0;
const unsigned char broadcast[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
struct in_addr zero = {0x00000000};
struct sockaddr_ll iaddr;
static char* print_hw_addr(const u_char* addr);
static char* print_ip_addr(const u_char* addr);
static char* print_arp_packet(struct arp_packet* pkt);
static int read_hw_addr(u_char* buf, const char* str);
static int read_ip_addr(struct in_addr* in_addr, const char* str);
char* program_name = NULL;
static void usage()
{
fprintf(stderr, "Usage: %s <-U -i <src_ip_addr> | -D -e <trg_ip_addr> "
"[-e <trg_ip_addr>] ...> [-c <count>] [-w <timeout>] "
"interface_name\n", program_name);
exit(EXC_USAGE);
}
static void parse_options (int argc, char **argv)
{
#define read_hw(addr) \
({ \
if (!strcmp(optarg, "broadcast")) \
memcpy(addr, broadcast, ETH_ALEN); \
else if (read_hw_addr(addr, optarg) < 0) \
usage(); \
addr##_flag = 1; \
})
#define read_ip(addr) \
({ \
if (read_ip_addr(&addr, optarg) < 0) \
usage(); \
addr##_flag = 1; \
})
int c;
static char short_options[] = "UDQPc:w:s:t:S:T:i:e:ov";
static struct option long_options[] =
{
{"update", 0, NULL, 'U'},
{"detect", 0, NULL, 'D'},
{"request", 0, NULL, 'Q'},
{"reply", 0, NULL, 'P'},
{"count", 1, NULL, 'c'},
{"timeout", 1, NULL, 'w'},
{"src-hw", 1, NULL, 's'},
{"trg-hw", 1, NULL, 't'},
{"src-arp", 1, NULL, 'S'},
{"trg-arp", 1, NULL, 'T'},
{"src-ip", 1, NULL, 'i'},
{"trg-ip", 1, NULL, 'e'},
{"at-once", 0, NULL, 'o'},
{NULL, 0, NULL, 0}
};
while ((c = getopt_long(argc, argv,
short_options, long_options, NULL)) != -1)
{
switch (c)
{
case 'U':
if (cmd)
usage();
cmd = AR_UPDATE;
break;
case 'D':
if (cmd)
usage();
cmd = AR_DETECT;
break;
case 'Q':
if (cmd)
usage();
cmd = AR_REQUEST;
break;
case 'P':
if (cmd)
usage();
cmd = AR_REPLY;
break;
case 'c':
count = atoi(optarg);
if (!count)
usage();
break;
case 'w':
timeout = atoi(optarg);
break;
case 's':
read_hw(src_hwaddr);
break;
case 't':
read_hw(trg_hwaddr);
break;
case 'S':
read_hw(src_arp_hwaddr);
break;
case 'T':
read_hw(trg_arp_hwaddr);
break;
case 'i':
if (strchr(optarg, ':')) {
ip6_addr = optarg;
src_ipaddr_flag = 1;
break;
}
read_ip(src_ipaddr);
break;
case 'e':
if (strchr(optarg, ':')) {
trg_ipaddr_flag = 1;
ip6_addr = optarg;
break;
}
if (trg_ipaddr_count >= MAX_IPADDR_NUMBER)
usage();
if (read_ip_addr(&trg_ipaddr[trg_ipaddr_count++],
optarg) < 0)
usage();
trg_ipaddr_flag = 1;
break;
case 'o':
at_once = 1;
break;
case 'v':
debug_level ++ ;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (cmd == AR_NOTHING || argc != 1
|| (cmd == AR_DETECT && !trg_ipaddr_flag)
|| (cmd == AR_UPDATE && !src_ipaddr_flag))
usage();
iface = argv[0];
/* user have to choose something */
if (!cmd)
usage();
if (cmd == AR_DETECT
&& trg_ipaddr_count <= 1) {
/* for detection with no more then one target
we exit on first reply */
at_once = 1;
}
}
u_char real_hwaddr[ETH_ALEN];
struct in_addr real_ipaddr;
static int init_device_addresses(int sock, const char* device)
{
struct ifreq ifr;
int ifindex;
short int ifflags;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, device, IFNAMSIZ-1);
if (ioctl(sock, SIOCGIFINDEX, &ifr) != 0) {
logger(LOG_ERROR, "unknown iface %s : %m", device);
return -1;
}
ifindex = ifr.ifr_ifindex;
if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
logger(LOG_ERROR, "ioctl(SIOCGIFFLAGS) : %m");
return -1;
}
ifflags = ifr.ifr_flags;
if (!(ifflags & IFF_UP)) {
logger(LOG_ERROR, "iface '%s' is down", device);
return -1;
}
if (ifflags & (IFF_NOARP|IFF_LOOPBACK)) {
logger(LOG_ERROR, "iface '%s' is not ARPable", device);
return -1;
}
if (ifflags & IFF_SLAVE) {
logger(LOG_ERROR, "iface '%s' is slave", device);
return -1;
}
/* get interface HW address */
if (ioctl(sock, SIOCGIFHWADDR, &ifr) != 0) {
logger(LOG_ERROR, "can't get iface '%s' HW address : %m", device);
return -1;
}
memcpy(real_hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
if (ioctl(sock, SIOCGIFADDR, &ifr)) {
logger(LOG_ERROR, "can't get iface '%s' address : %m", device);
return -1;
}
memcpy(&real_ipaddr, ifr.ifr_addr.sa_data + 2, IP_ADDR_LEN);
logger(LOG_DEBUG, "got addresses hw='%s', ip='%s'",
print_hw_addr(real_hwaddr),
print_ip_addr((u_char*) &real_ipaddr));
/* fill interface description struct */
iaddr.sll_ifindex = ifindex;
iaddr.sll_family = AF_PACKET;
iaddr.sll_protocol = htons(ETH_P_ARP);
memcpy(iaddr.sll_addr, real_hwaddr, iaddr.sll_halen = ETH_ALEN);
return 0;
}
int sock;
/* sent packet */
struct arp_packet pkt;
static void create_arp_packet(struct arp_packet* pkt)
{
#define set_ip(to, from) (memcpy((to), (from), IP_ADDR_LEN))
#define set_hw(to, from) (memcpy((to), (from), ETH_ALEN))
#define check(check, two) (check##_flag ? check : (two))
pkt->frame_type = htons(ETH_P_ARP);
pkt->hw_type = htons(ARPHRD_ETHER);
pkt->prot_type = htons(ETH_P_IP);
pkt->hw_addr_size = ETH_ALEN;
pkt->prot_addr_size = IP_ADDR_LEN;
pkt->op = htons(cmd == AR_REPLY ? REPLY : REQUEST);
set_ip(pkt->sndr_ip_addr,
(src_ipaddr_flag ? &src_ipaddr : &real_ipaddr));
set_hw(pkt->targ_hw_addr, check(trg_hwaddr, broadcast));
set_hw(pkt->src_hw_addr, check(src_hwaddr, real_hwaddr));
set_hw(pkt->rcpt_hw_addr, check(trg_arp_hwaddr, pkt->targ_hw_addr));
set_hw(pkt->sndr_hw_addr, check(src_arp_hwaddr, pkt->src_hw_addr));
/* Special case. Because we support multiple recipient IP addresses
we set up 'pkt.rcpt_ip_addr' separately for each of the specified
(using -e option) 'trg_ipaddr'
*/
if (trg_ipaddr_flag) {
/* at least one trg_ipaddr is specified */
set_ip(pkt->rcpt_ip_addr, &trg_ipaddr[0]);
} else {
set_ip(pkt->rcpt_ip_addr, &real_ipaddr);
/* set 'trg_ipaddr' for checking incoming packets */
set_ip(&trg_ipaddr[0], &real_ipaddr);
trg_ipaddr_count = 1;
}
#undef set_ip
}
static void set_trg_ipaddr(struct arp_packet* pkt, const struct in_addr ipaddr)
{
memcpy(pkt->rcpt_ip_addr, &ipaddr, IP_ADDR_LEN);
}
int recv_response = 0;
static void finish()
{
switch (cmd)
{
case AR_DETECT:
case AR_UPDATE:
exit((recv_response) ? EXC_RECV : EXC_OK);
case AR_REQUEST:
exit((recv_response) ? EXC_OK : EXC_NORECV);
case AR_REPLY:
exit(EXC_OK);
default:
exit(EXC_SYS);
}
}
static int recv_pack(void *buf, int len, struct sockaddr_ll *from)
{
int rc = -1;
int i;
struct arp_packet * recv_pkt = (struct arp_packet*) buf;
if (recv_pkt->frame_type != htons(ETH_P_ARP)) {
logger(LOG_ERROR, "unknown eth frame type : %#x",
recv_pkt->frame_type);
goto out;
}
if (recv_pkt->op != htons(REQUEST)
&& recv_pkt->op != htons(REPLY)) {
logger(LOG_ERROR, "unknown arp packet type : %#x",
recv_pkt->op);
goto out;
}
for (i = 0; i < trg_ipaddr_count; i ++) {
// check for all sent packets addresses
if (memcmp(&trg_ipaddr[i], recv_pkt->sndr_ip_addr,
IP_ADDR_LEN))
continue;
logger(LOG_DEBUG, "recv packet %s",
print_arp_packet(recv_pkt));
logger(LOG_INFO, "%s is detected on another computer : %s",
print_ip_addr((u_char*) &trg_ipaddr[i]),
print_hw_addr(recv_pkt->sndr_hw_addr));
recv_response++;
/* finish on first response */
if (at_once)
finish();
}
/* unknown packet */
logger(LOG_DEBUG, "recv unknown packet %s",
print_arp_packet(recv_pkt));
rc = 0;
out:
return rc;
}
static void sender(void)
{
int i;
if (count-- == 0)
finish();
/* send multiple packets */
for (i = 0; i < trg_ipaddr_count; i ++) {
set_trg_ipaddr(&pkt, trg_ipaddr[i]);
logger(LOG_DEBUG, "send packet: %s", print_arp_packet(&pkt));
if (sendto(sock, &pkt, sizeof(pkt), 0,
(struct sockaddr*) &iaddr, sizeof(iaddr)) < 0) {
logger(LOG_ERROR, "sendto : %m");
exit(EXC_SYS);
}
}
if (count == 0
&& cmd != AR_DETECT
&& cmd != AR_REQUEST)
finish();
alarm(timeout);
}
static void set_signal(int signo, void (*handler)(void))
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = (void (*)(int))handler;
sa.sa_flags = SA_RESTART;
sigaction(signo, &sa, NULL);
}
int main(int argc, char** argv)
{
sigset_t block_alarm;
program_name = argv[0];
parse_options (argc, argv);
if (ip6_addr) {
if (cmd == AR_UPDATE)
execlp(SBINDIR "/ndsend", "ndsend",
ip6_addr, iface, NULL);
exit(0);
}
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (sock < 0)
{
logger(LOG_ERROR, "socket : %m");
exit(EXC_SYS);
}
if (init_device_addresses(sock, iface) < 0)
exit(EXC_SYS);
create_arp_packet(&pkt);
set_signal(SIGALRM, sender);
sigemptyset(&block_alarm);
sigaddset(&block_alarm, SIGALRM);
sender();
while(1)
{
u_char packet[4096];
struct sockaddr_ll from;
socklen_t alen = sizeof(from);
int cc;
cc = recvfrom(sock, packet, sizeof(packet), 0,
(struct sockaddr *)&from, &alen);
if (cc < 0)
{
logger(LOG_ERROR, "recvfrom : %m");
continue;
}
sigprocmask (SIG_BLOCK, &block_alarm, NULL);
recv_pack(packet, cc, &from);
sigprocmask (SIG_UNBLOCK, &block_alarm, NULL);
}
exit(EXC_OK);
}
static char* get_buf()
{
static int num = 0;
static char buf[10][1024];
return buf[num = (num+1) % 10];
}
static char* print_arp_packet(struct arp_packet* pkt)
{
char* point = get_buf();
sprintf(point, "eth '%s' -> eth '%s'; "
"arp sndr '%s' '%s'; %s; arp recipient '%s' '%s'",
print_hw_addr(pkt->src_hw_addr),
print_hw_addr(pkt->targ_hw_addr),
print_hw_addr(pkt->sndr_hw_addr),
print_ip_addr(pkt->sndr_ip_addr),
(pkt->op == htons(REQUEST)) ? "request" :
(pkt->op == htons(REPLY) ? "reply" : "unknown"),
print_hw_addr(pkt->rcpt_hw_addr),
print_ip_addr(pkt->rcpt_ip_addr));
return point;
}
/* get MAC address in user form */
static char* print_hw_addr(const u_char* addr)
{
int i;
char* point = get_buf();
char* current = point;
for (i = 0; i < ETH_ALEN; i++)
point += sprintf(point, i == (ETH_ALEN-1) ? "%02x" : "%02x:",
addr[i]);
return current;
}
/* get IP address in user form */
static char* print_ip_addr(const u_char* addr)
{
int i;
char* point = get_buf();
char* current = point;
for (i = 0; i < IP_ADDR_LEN; i++)
point += sprintf(point, i == (IP_ADDR_LEN-1) ? "%u" : "%u.",
addr[i]);
return current;
}
/* Transform user -> computer friendly MAC address */
static int read_hw_addr(u_char* buf, const char* str)
{
int rc = -1;
int i;
for(i = 0; i < 2*ETH_ALEN; i++)
{
char c, val;
c = tolower(*str++);
if (!c)
goto out;
if (isdigit(c))
val = c - '0';
else if (c >= 'a' && c <= 'f')
val = c - 'a' + 10;
else
goto out;
if (i % 2)
{
*buf++ |= val;
if (*str == ':')
str++;
}
else
*buf = val << 4;
}
rc = 0;
out:
return rc;
}
/* Transform user -> computer friendly IP address */
static int read_ip_addr(struct in_addr* in_addr, const char* str)
{
in_addr->s_addr=inet_addr(str);
if (in_addr->s_addr == INADDR_NONE)
return -1;
return 0;
}