/*
* arp-scan is Copyright (C) 2005-2025 Roy Hills
*
* This file is part of arp-scan.
*
* arp-scan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* arp-scan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with arp-scan. If not, see .
*
* arp-scan -- Send ARP requests to network hosts and display responses
*
* Author: Roy Hills
* Date: 13 October 2005
*
* Usage:
* arp-scan [options] [host...]
*
* Description:
*
* arp-scan sends the specified ARP packet to the specified hosts
* and displays any responses received.
*
* The ARP protocol is defined in RFC 826 Ethernet Address Resolution Protocol
*
*/
#include "arp-scan.h"
/* Global variables */
static host_entry *helist = NULL; /* Array of host entries */
static host_entry **helistptr; /* Array of pointers to host entries */
static host_entry **cursor; /* Pointer to current host entry ptr */
static unsigned num_hosts = 0; /* Number of entries in the list */
static unsigned responders = 0; /* Number of hosts which responded */
static unsigned live_count; /* Number of entries awaiting reply */
static int verbose = 0; /* Verbose level */
static char *filename; /* Target list file name */
static int filename_flag = 0; /* Set if using target list file */
static int random_flag = 0; /* Randomise the list */
static int numeric_flag = 0; /* IP addresses only */
static unsigned interval = 0; /* Desired interval between packets */
static unsigned bandwidth = DEFAULT_BANDWIDTH; /* Bandwidth in bits per sec */
static unsigned retry = DEFAULT_RETRY; /* Number of retries */
static unsigned timeout = DEFAULT_TIMEOUT; /* Per-host timeout */
static float backoff_factor = DEFAULT_BACKOFF_FACTOR; /* Backoff factor */
static int snaplen = SNAPLEN; /* Pcap snap length */
static char *if_name = NULL; /* Interface name, e.g. "eth0" */
static int quiet_flag = 0; /* Don't decode the packet */
static int ignore_dups = 0; /* Don't display duplicate packets */
static uint32_t arp_spa; /* Source IP address */
static int arp_spa_flag = 0; /* Source IP address specified */
static int arp_spa_is_tpa = 0; /* Source IP is dest IP */
static unsigned char arp_sha[ETH_ALEN]; /* Source Ethernet MAC Address */
static int arp_sha_flag = 0; /* Source MAC address specified */
static char *ouifilename = NULL; /* OUI filename */
static char *macfilename = NULL; /* MAC filename */
static char *pcap_savefile = NULL; /* pcap savefile filename */
static int arp_op = DEFAULT_ARP_OP; /* ARP Operation code */
static int arp_hrd = DEFAULT_ARP_HRD; /* ARP hardware type */
static int arp_pro = DEFAULT_ARP_PRO; /* ARP protocol */
static int arp_hln = DEFAULT_ARP_HLN; /* Hardware address length */
static int arp_pln = DEFAULT_ARP_PLN; /* Protocol address length */
static int eth_pro = DEFAULT_ETH_PRO; /* Ethernet protocol type */
static unsigned char arp_tha[6] = {0, 0, 0, 0, 0, 0};
static unsigned char target_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
static unsigned char source_mac[6];
static int source_mac_flag = 0;
static unsigned char *padding = NULL;
static size_t padding_len = 0;
static int localnet_flag = 0; /* Scan local network */
static int llc_flag = 0; /* Use 802.2 LLC with SNAP */
static int ieee_8021q_vlan = -1; /* Use 802.1Q VLAN tagging if >= 0 */
static int pkt_write_file_flag = 0; /* Write packet to file flag */
static int pkt_read_file_flag = 0; /* Read packet from file flag */
static char *pkt_filename = NULL; /* Read/Write packet to file filename */
static int write_pkt_to_file = 0; /* Write packet to file for debugging */
static int rtt_flag = 0; /* Display round-trip time */
static pcap_dumper_t *pcap_dump_handle = NULL; /* pcap savefile handle */
static int plain_flag = 0; /* Only show host information */
static int resolve_flag = 0; /* Resolve IP addresses to hostnames */
unsigned int random_seed = 0;
static unsigned retry_send = DEFAULT_RETRY_SEND; /* Number of send packet retries */
static unsigned retry_send_interval = DEFAULT_RETRY_SEND_INTERVAL; /* Interval in seconds between send packet retries */
static unsigned int host_limit = 0; /* Exit after n responders if nonzero */
static format_element *format = NULL; /* Output format linked list */
static int exclude_broadcast = 0; /* Exclude network & broadcast address */
int
main(int argc, char *argv[]) {
struct timeval now;
struct timeval diff; /* Difference between two timevals */
int select_timeout; /* Select timeout */
uint64_t loop_timediff; /* Time since last packet sent in us */
uint64_t host_timediff; /* Time since last pkt sent to this host (us) */
struct timeval last_packet_time; /* Time last packet was sent */
int req_interval; /* Requested per-packet interval */
int cum_err = 0; /* Cumulative timing error */
struct timeval start_time; /* Program start time */
struct timeval end_time; /* Program end time */
struct timeval elapsed_time; /* Elapsed time as timeval */
double elapsed_seconds; /* Elapsed time in seconds */
int reset_cum_err;
int pass_no = 0;
int first_timeout = 1;
unsigned i;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program filter;
char *filter_string;
bpf_u_int32 netmask;
bpf_u_int32 localnet;
int datalink;
int ret_status = 0;
int pcap_fd; /* Pcap file descriptor */
unsigned char interface_mac[ETH_ALEN];
pcap_t *pcap_handle; /* pcap handle */
struct in_addr interface_ip_addr;
/*
* Limit process capabilities to the minimum necessary to run this program.
*
* If we have POSIX.1e capability support, this removes all capabilities
* from the effective set and reduces the capabilities in the permitted
* set to the minimum needed.
*
* If we do not have capability support, then drop any SUID root privs
* by setting the effective user id to the real uid.
*/
limit_capabilities();
/*
* Process options.
*/
process_options(argc, argv);
/*
* If we're not reading from a file, and --localnet was not specified, then
* die if no hosts were given as command line arguments.
*/
if (!filename_flag && !localnet_flag)
if ((argc - optind) < 1)
err_msg("ERROR: No target hosts on command line and neither --file or "
"--localnet options given");
/*
* Get program start time for statistics displayed on completion.
*/
Gettimeofday(&start_time);
/*
* Open the network device for reading with pcap, or the pcap file if we
* have specified --readpktfromfile. If we are writing packets to a binary
* file, then set pcap_handle to NULL as we don't need to read packets in
* this case.
*/
if (pkt_read_file_flag) { /* Reading packets from pcap file */
if (!(pcap_handle = pcap_open_offline(pkt_filename, errbuf)))
err_msg("pcap_open_offline: %s", errbuf);
} else if (!pkt_write_file_flag) { /* Reading packets from network */
/*
* enable CAP_NET_RAW in the effective set if we have POSIX.1e capability
* support. If we don't have capability support then restore SUID root
* privs by setting the effective user id to the saved euid.
*/
set_capability(ENABLE);
/*
* Determine network interface to use. If the interface was specified
* with the --interface option then use that, otherwise use
* my_lookupdev() to pick a suitable interface.
*
*/
if (!if_name) {
if (!(if_name = my_lookupdev(errbuf))) {
err_msg("my_lookupdev: %s", errbuf);
}
}
if (!(pcap_handle = pcap_create(if_name, errbuf)))
err_msg("pcap_create: %s", errbuf);
if ((pcap_set_snaplen(pcap_handle, snaplen)) < 0)
err_msg("pcap_set_snaplen: %s", pcap_geterr(pcap_handle));
if ((pcap_set_promisc(pcap_handle, PROMISC)) < 0)
err_msg("pcap_set_promisc: %s", pcap_geterr(pcap_handle));
if ((pcap_set_immediate_mode(pcap_handle, 1)) < 0)
err_msg("pcap_set_immediate_mode: %s", pcap_geterr(pcap_handle));
if ((pcap_set_timeout(pcap_handle, TO_MS)) < 0) /* Is this still needed? */
err_msg("pcap_set_timeout: %s", pcap_geterr(pcap_handle));
ret_status = pcap_activate(pcap_handle);
if (ret_status < 0) { /* Error from pcap_activate() */
char *cp;
cp = pcap_geterr(pcap_handle);
if (ret_status == PCAP_ERROR)
err_msg("pcap_activate: %s", cp);
else if ((ret_status == PCAP_ERROR_NO_SUCH_DEVICE ||
ret_status == PCAP_ERROR_PERM_DENIED) && *cp != '\0')
err_msg("pcap_activate: %s: %s\n(%s)", if_name,
pcap_statustostr(ret_status), cp);
else
err_msg("pcap_activate: %s: %s", if_name,
pcap_statustostr(ret_status));
} else if (ret_status > 0) { /* Warning from pcap_activate() */
char *cp;
cp = pcap_geterr(pcap_handle);
if (ret_status == PCAP_WARNING)
warn_msg("pcap_activate: %s", cp);
else if (ret_status == PCAP_WARNING_PROMISC_NOTSUP && *cp != '\0')
warn_msg("pcap_activate: %s: %s\n(%s)", if_name,
pcap_statustostr(ret_status), cp);
else
warn_msg("pcap_activate: %s: %s", if_name,
pcap_statustostr(ret_status));
}
/*
* Obtain the MAC address for the selected interface, and use this
* as the default value for the source hardware addresses in the frame
* header and ARP packet if the user has not specified their values.
*/
get_hardware_address(if_name, interface_mac);
/*
* Disable CAP_NET_RAW in the effective set if we have POSIX.1e capability
* support. If we don't have capability support then drop SUID root
* privs by setting the effective user id to the real uid.
*/
set_capability(DISABLE);
/*
* Permanently remove all capabilities or SUID root privilege as we
* don't need any special privileges after this point.
*
* We disable all capabilities in both the effective and permitted sets
* if we have POSIX.1e capability support, otherwise we permanently drop
* SUID root privs by setting the user ID to the real user ID.
*/
drop_capabilities();
/*
* Die with an error if we can't get the MAC address, as this
* indicates that the interface doesn't have a MAC address, so is
* probably not a compatible interface type.
*/
if (interface_mac[0]==0 && interface_mac[1]==0 &&
interface_mac[2]==0 && interface_mac[3]==0 &&
interface_mac[4]==0 && interface_mac[5]==0) {
err_msg("ERROR: Could not obtain MAC address for interface %s",
if_name);
}
if (source_mac_flag == 0)
memcpy(source_mac, interface_mac, ETH_ALEN);
if (arp_sha_flag == 0)
memcpy(arp_sha, interface_mac, ETH_ALEN);
/*
* Obtain the interface IP address, and use that as the default value
* if the user has not manually specified the ARP source address.
*
* Give a warning and use 0.0.0.0 if the interface has no IP address.
*/
ret_status = get_source_ip(if_name, &interface_ip_addr);
if (arp_spa_flag == 0) {
if (ret_status == -1) {
warn_msg(
"WARNING: Could not obtain IP address for interface %s. "
"Using 0.0.0.0 for\nthe source address, which may not be "
"what you want. Either configure\n"
"%s with an IP address, or specify the address "
"with the --arpspa option.",if_name, if_name);
}
memcpy(&arp_spa, &(interface_ip_addr.s_addr), sizeof(arp_spa));
}
} else { /* Not reading packets because we are writing to pcap file */
pcap_handle = NULL;
}
/*
* If we are reading data with pcap, get and display the datalink details
*/
if (pcap_handle) {
if ((datalink = pcap_datalink(pcap_handle)) < 0)
err_msg("pcap_datalink: %s", pcap_geterr(pcap_handle));
if (!plain_flag) {
if (!pkt_read_file_flag) {
printf("Interface: %s, type: %s, "
"MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x, IPv4: %s\n",
if_name, pcap_datalink_val_to_name(datalink),
interface_mac[0], interface_mac[1], interface_mac[2],
interface_mac[3], interface_mac[4], interface_mac[5],
(interface_ip_addr.s_addr==0) ? "(none)" : my_ntoa(interface_ip_addr));
} else {
printf("Interface: pcap file\n");
}
}
if (datalink != DLT_EN10MB) {
warn_msg("WARNING: Unsupported datalink type");
}
}
/*
* If we are reading from a network device, then get the associated file
* descriptor and configure it, determine the interface IP network and
* netmask, and install a pcap filter to receive only ARP responses.
* If we are reading from a pcap file, or writing to a binary file, just
* set the file descriptor to -1 to indicate that it is not associated
* with a network device.
*/
if (!pkt_read_file_flag && !pkt_write_file_flag) {
if ((pcap_fd = pcap_get_selectable_fd(pcap_handle)) < 0)
err_msg("pcap_fileno: %s", pcap_geterr(pcap_handle));
if ((pcap_setnonblock(pcap_handle, 1, errbuf)) < 0)
err_msg("pcap_setnonblock: %s", errbuf);
if (pcap_lookupnet(if_name, &localnet, &netmask, errbuf) < 0) {
memset(&localnet, '\0', sizeof(localnet));
memset(&netmask, '\0', sizeof(netmask));
if (localnet_flag) {
err_msg("ERROR: Could not obtain interface IP address and "
"netmask: pcap_lookupnet: %s", errbuf);
}
}
/*
* The pcap filter string selects packets addressed to the ARP source
* address that are Ethernet-II ARP packets, 802.3 LLC/SNAP ARP packets,
* 802.1Q tagged ARP packets or 802.1Q tagged 802.3 LLC/SNAP ARP packets.
*/
filter_string=make_message("ether dst %.2x:%.2x:%.2x:%.2x:%.2x:%.2x and "
"(arp or (ether[14:4]=0xaaaa0300 and "
"ether[20:2]=0x0806) or (ether[12:2]=0x8100 "
"and ether[16:2]=0x0806) or "
"(ether[12:2]=0x8100 and "
"ether[18:4]=0xaaaa0300 and "
"ether[24:2]=0x0806))",
arp_sha[0], arp_sha[1],
arp_sha[2], arp_sha[3],
arp_sha[4], arp_sha[5]);
if (verbose > 1)
warn_msg("DEBUG: pcap filter string: \"%s\"", filter_string);
if ((pcap_compile(pcap_handle, &filter, filter_string, OPTIMISE,
netmask)) < 0)
err_msg("pcap_compile: %s", pcap_geterr(pcap_handle));
free(filter_string);
if ((pcap_setfilter(pcap_handle, &filter)) < 0)
err_msg("pcap_setfilter: %s", pcap_geterr(pcap_handle));
pcap_freecode(&filter);
} else { /* Reading packets from file */
pcap_fd = -1;
}
/*
* Open pcap savefile is the --pcapsavefile (-W) option was specified
*/
if (pcap_savefile) {
if (!(pcap_dump_handle = pcap_dump_open(pcap_handle, pcap_savefile))) {
err_msg("pcap_dump_open: %s", pcap_geterr(pcap_handle));
}
}
/*
* Check that the combination of specified options and arguments is
* valid.
*/
if (interval && bandwidth != DEFAULT_BANDWIDTH)
err_msg("ERROR: You cannot specify both --bandwidth and --interval.");
if (localnet_flag) {
if ((argc - optind) > 0)
err_msg("ERROR: You can not specify targets with the --localnet option");
if (filename_flag)
err_msg("ERROR: You can not specify both --file and --localnet options");
}
/*
* Create MAC/Vendor hash table if quiet is not in effect.
*/
if (!quiet_flag) {
char *fn;
int count;
if ((hcreate(HASH_TABLE_SIZE)) == 0)
err_sys("hcreate");
fn = get_mac_vendor_filename(ouifilename, PKGDATADIR, OUIFILENAME);
count = add_mac_vendor(fn);
if (verbose > 1 && count > 0)
warn_msg("DEBUG: Loaded %d IEEE OUI/Vendor entries from %s.",
count, fn);
free(fn);
fn = get_mac_vendor_filename(macfilename, PKGSYSCONFDIR, MACFILENAME);
count = add_mac_vendor(fn);
if (verbose > 1 && count > 0)
warn_msg("DEBUG: Loaded %d MAC/Vendor entries from %s.",
count, fn);
free(fn);
}
/*
* Populate the list from the specified file if --file was specified, or
* from the interface address and mask if --localnet was specified, or
* otherwise from the remaining command line arguments.
*/
if (filename_flag) { /* Populate list from file */
FILE *fp;
char line[MAXLINE];
char *cp;
if ((strcmp(filename, "-")) == 0) { /* Filename "-" means stdin */
fp = stdin;
} else {
if ((fp = fopen(filename, "r")) == NULL) {
err_sys("Cannot open %s", filename);
}
}
while (fgets(line, MAXLINE, fp)) {
for (cp = line; !isspace((unsigned char)*cp) && *cp != '\0'; cp++)
;
*cp = '\0';
add_host_pattern(line, timeout);
}
if (fp != stdin) {
fclose(fp);
}
} else if (localnet_flag) { /* Populate list from i/f addr & mask */
struct in_addr if_network;
struct in_addr if_netmask;
char *c_network;
char *c_netmask;
const char *cp;
char localnet_descr[32];
if_network.s_addr = localnet;
if_netmask.s_addr = netmask;
cp = my_ntoa(if_network);
c_network = make_message("%s", cp);
cp = my_ntoa(if_netmask);
c_netmask = make_message("%s", cp);
snprintf(localnet_descr, 32, "%s:%s", c_network, c_netmask);
if (!plain_flag) {
printf("Target list from interface: network %s netmask %s\n",
c_network, c_netmask);
}
free(c_network);
free(c_netmask);
add_host_pattern(localnet_descr, timeout);
} else { /* Populate list from command line arguments */
argv = &argv[optind];
while (*argv) {
add_host_pattern(*argv, timeout);
argv++;
}
}
/*
* Check that we have at least one entry in the list.
*/
if (!num_hosts)
err_msg("ERROR: No hosts to process.");
/*
* If --writepkttofile was specified, open the specified output file.
*/
if (pkt_write_file_flag) {
write_pkt_to_file = open(pkt_filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
if (write_pkt_to_file == -1)
err_sys("open %s", pkt_filename);
}
/*
* If we have the OpenBSD pledge(2) system call, use it to restrict
* system operations from this point.
*/
#ifdef HAVE_PLEDGE
if (pledge("stdio dns bpf", NULL) == -1)
err_sys("pledge");
#endif
/*
* Create and initialise array of pointers to host entries.
*/
helistptr = Malloc(num_hosts * sizeof(host_entry *));
for (i=0; i0; i--) {
r = (int)(genrand_real2() * i); /* 0<=r 1) {
warn_msg("DEBUG: pkt len=%zu bytes, bandwidth=%u bps, interval=%u us",
packet_out_len, bandwidth, interval);
}
}
/*
* Display initial message.
*/
if (!plain_flag) {
printf("Starting %s with %u hosts (https://github.com/royhills/arp-scan)\n",
PACKAGE_STRING, num_hosts);
}
/*
* Display the lists if verbose setting is 3 or more.
*/
if (verbose > 2)
dump_list();
/*
* Main loop: send packets to all hosts in order until a response
* has been received or the host has exhausted its retry limit.
*
* The loop exits when all hosts have either responded or timed out;
* or if the number of responders reaches host_limit when host_limit is
* non zero.
*/
reset_cum_err = 1;
req_interval = interval;
while (live_count && !(host_limit != 0 && responders >= host_limit)) {
/*
* Obtain current time and calculate deltas since last packet and
* last packet to this host.
*/
Gettimeofday(&now);
/*
* If the last packet was sent more than interval microseconds ago, we
* can potentially send a packet to the current host.
*/
timeval_diff(&now, &last_packet_time, &diff);
loop_timediff = (uint64_t)1000000*diff.tv_sec + diff.tv_usec;
if (loop_timediff >= (unsigned)req_interval) {
/*
* If the last packet to this host was sent more than the current
* timeout for this host us ago, then we can potentially send a packet
* to it.
*/
timeval_diff(&now, &((*cursor)->last_send_time), &diff);
host_timediff = (uint64_t)1000000*diff.tv_sec + diff.tv_usec;
if (host_timediff >= (*cursor)->timeout) {
if (reset_cum_err) {
cum_err = 0;
req_interval = interval;
reset_cum_err = 0;
} else {
cum_err += loop_timediff - interval;
req_interval = (req_interval>=cum_err) ? req_interval-cum_err : 0;
}
select_timeout = req_interval;
/*
* If we've exceeded our retry limit, this host has timed out so
* remove it from the list. Otherwise increase the timeout by the
* backoff factor if this is not the first packet sent to this host
* and send a packet.
*/
if (verbose && (*cursor)->num_sent > pass_no) {
warn_msg("---\tPass %d complete", pass_no+1);
pass_no = (*cursor)->num_sent;
}
if ((*cursor)->num_sent >= retry) {
if (verbose > 1)
warn_msg("---\tRemoving host %s - Timeout",
my_ntoa((*cursor)->addr));
remove_host(cursor); /* Automatically calls advance_cursor() */
if (first_timeout) {
timeval_diff(&now, &((*cursor)->last_send_time), &diff);
host_timediff = (uint64_t)1000000*diff.tv_sec +
diff.tv_usec;
while (host_timediff >= (*cursor)->timeout && live_count) {
if ((*cursor)->live) {
if (verbose > 1)
warn_msg("---\tRemoving host %s - Catch-Up Timeout",
my_ntoa((*cursor)->addr));
remove_host(cursor);
} else {
advance_cursor();
}
timeval_diff(&now, &((*cursor)->last_send_time), &diff);
host_timediff = (uint64_t)1000000*diff.tv_sec +
diff.tv_usec;
}
first_timeout = 0;
}
Gettimeofday(&last_packet_time);
} else { /* Retry limit not reached for this host */
if ((*cursor)->num_sent)
(*cursor)->timeout *= backoff_factor;
send_packet(pcap_handle, *cursor, &last_packet_time);
advance_cursor();
}
} else { /* We can't send a packet to this host yet */
/*
* There is no point calling advance_cursor() here because if
* host n is not ready to send host n+1 will not be ready either.
*/
select_timeout = (*cursor)->timeout - host_timediff;
reset_cum_err = 1; /* Zero cumulative error */
} /* End If */
} else { /* We can't send a packet yet */
select_timeout = req_interval - loop_timediff;
} /* End If */
recvfrom_wto(pcap_fd, select_timeout, pcap_handle);
} /* End While */
if (!plain_flag) {
printf("\n"); /* Ensure we have a blank line */
}
clean_up(pcap_handle);
if (write_pkt_to_file)
close(write_pkt_to_file);
Gettimeofday(&end_time);
timeval_diff(&end_time, &start_time, &elapsed_time);
elapsed_seconds = (elapsed_time.tv_sec*1000 +
elapsed_time.tv_usec/1000) / 1000.0;
if (!plain_flag) {
printf("Ending %s: %u hosts scanned in %.3f seconds (%.2f hosts/sec). %u "
"responded\n",
PACKAGE_STRING, num_hosts, elapsed_seconds,
num_hosts/elapsed_seconds, responders);
}
/*
* exit with status 1 if host_limit has been set with the --limit option and
* the number of responding hosts is less than this limit. Otherwise exit
* with status 0.
*/
return (host_limit == 0 || responders >= host_limit) ? 0 : 1;
}
/*
* display_packet -- Check and display received packet
*
* Inputs:
*
* he The host entry corresponding to the received packet
* arpei ARP packet structure
* extra_data Extra data after ARP packet (padding)
* extra_data_len Length of extra data
* framing Framing type (e.g. Ethernet II, LLC)
* vlan_id 802.1Q VLAN identifier, or -1 if not 802.1Q
* frame_hdr The Ethernet frame header
* pcap_header The PCAP header struct
*
* Returns:
*
* None.
*
* This checks the received packet and displays details of what
* was received in the format: .
*/
void
display_packet(host_entry *he, arp_ether_ipv4 *arpei,
const unsigned char *extra_data, size_t extra_data_len,
int framing, int vlan_id, ether_hdr *frame_hdr,
const struct pcap_pkthdr *pcap_header) {
typedef struct {
const char *name;
char *value;
} field;
static field fields[NUMFIELDS] = {
{"IP",NULL}, {"Name",NULL}, {"MAC",NULL}, {"HdrMAC",NULL},
{"Vendor",NULL}, {"Padding",NULL}, {"Framing",NULL}, {"VLAN",NULL},
{"Proto",NULL}, {"DUP",NULL}, {"RTT",NULL}, {"IPnum",NULL},
};
static const id_name_map fields_map[] = {
{0, "IP"}, {1, "Name"}, {2, "MAC"},
{3, "HdrMAC"}, {4, "Vendor"}, {5, "Padding"},
{6, "Framing"}, {7, "VLAN"}, {8, "Proto"},
{9, "DUP"}, {10, "RTT"}, {11, "IPnum"},
{-1, NULL} /* -1 marks end of list */
};
char *msg;
char *cp;
char *ga_err_msg;
int nonzero = 0;
unsigned i;
/*
* Assign output fields based on response packet and options.
*/
/*
* IP field, always present.
*/
fields[0].value = make_message("%s", my_ntoa(he->addr));
/*
* Name field, present if --resolve option given.
*/
if (resolve_flag) {
cp = get_host_name(he->addr, &ga_err_msg);
if (cp) {
fields[1].value = make_message("%s", cp);
} else {
warn_msg("WARNING: getnameinfo() failed for \"%s\": %s",
my_ntoa(he->addr), ga_err_msg);
}
}
/*
* MAC field, always present.
*/
fields[2].value = make_message("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
arpei->ar_sha[0], arpei->ar_sha[1],
arpei->ar_sha[2], arpei->ar_sha[3],
arpei->ar_sha[4], arpei->ar_sha[5]);
/*
* HdrMAC field, present if source MAC in the ARP packet is different
* to source MAC in the Ethernet frame header.
*/
if ((memcmp(arpei->ar_sha, frame_hdr->src_addr, ETH_ALEN)) != 0) {
fields[3].value = make_message("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
frame_hdr->src_addr[0], frame_hdr->src_addr[1],
frame_hdr->src_addr[2], frame_hdr->src_addr[3],
frame_hdr->src_addr[4], frame_hdr->src_addr[5]);
}
/*
* Vendor field, present if --quiet option not given
*/
if (!quiet_flag) {
/*
* Find vendor in hash table.
*
* We start with more specific matches (against larger parts of the
* hardware address), and work towards less specific matches until
* we find a match or exhaust all possible matches.
*/
char oui_string[13]; /* Space for full hw addr plus NULL */
const char *vendor = NULL;
int oui_end = 12;
ENTRY hash_query;
ENTRY *hash_result;
snprintf(oui_string, 13, "%.2X%.2X%.2X%.2X%.2X%.2X",
arpei->ar_sha[0], arpei->ar_sha[1], arpei->ar_sha[2],
arpei->ar_sha[3], arpei->ar_sha[4], arpei->ar_sha[5]);
while (vendor == NULL && oui_end > 1) {
oui_string[oui_end] = '\0'; /* Truncate oui string */
hash_query.key = oui_string;
hash_result = hsearch(hash_query, FIND);
if (hash_result) {
vendor = hash_result->data;
} else {
vendor = NULL;
}
oui_end--;
}
if (vendor)
fields[4].value = make_message("%s", vendor);
else
/* Check the second-least-significant bit of first octet */
if (arpei->ar_sha[0] & (1<<1))
fields[4].value = make_message("%s", "(Unknown: locally administered)");
else
fields[4].value = make_message("%s", "(Unknown)");
/*
* Padding field, present if --quiet option not given and frame padding
* is non zero
*/
/*
* Check that any data after the ARP packet is zero.
* If it is non-zero, and verbose is selected, then set the Padding
* field to the hex representation of the padding.
*/
if (extra_data_len > 0) {
const unsigned char *ucp = extra_data;
for (i=0; iar_pro) != 0x0800) {
fields[8].value = make_message("0x%04x", ntohs(arpei->ar_pro));
}
/*
* DUP field, present if this is not the first response from this host.
*/
if (he->num_recv > 1) {
fields[9].value = make_message("%u", he->num_recv);
}
/*
* RTT field, present if the --rtt option is given
*/
if (rtt_flag) {
struct timeval rtt;
struct timeval pcap_timestamp;
unsigned long rtt_us; /* round-trip time in microseconds */
/*
* We can't pass a pointer to pcap_header->ts directly to timeval_diff
* because it may not have the same size as a struct timeval.
* E.g. OpenBSD 5.1 on amd64.
*/
pcap_timestamp.tv_sec = pcap_header->ts.tv_sec;
pcap_timestamp.tv_usec = pcap_header->ts.tv_usec;
timeval_diff(&pcap_timestamp, &(he->last_send_time), &rtt);
rtt_us = rtt.tv_sec * 1000000 + rtt.tv_usec;
fields[10].value=make_message("%lu.%03lu", rtt_us/1000, rtt_us%1000);
}
} /* End if (!quiet_flag) */
/*
* IPnum field, always present
*/
fields[11].value=make_message("%lu", ntohl(he->addr.s_addr));
/*
* Output fields.
*/
if (!format) { /* If --format option not given */
/*
* Output IP field or Name field depending on whether --resolve option
* was given.
*/
if (resolve_flag) {
msg = make_message("%s", fields[1].value);
} else {
msg = make_message("%s", fields[0].value);
}
/*
* Output MAC field
*/
cp = msg;
msg = make_message("%s\t%s", cp, fields[2].value);
free(cp);
/*
* Output HdrMAC field if present
*/
if (fields[3].value) {
cp = msg;
msg = make_message("%s (%s)", cp, fields[3].value);
free(cp);
}
/*
* Output Vendor field if present.
*/
if (fields[4].value) {
cp = msg;
msg = make_message("%s\t%s", cp, fields[4].value);
free(cp);
}
/*
* Output Padding field if present and --verbose is given
*/
if (fields[5].value && verbose) {
cp = msg;
msg = make_message("%s\tPadding=%s", cp, fields[5].value);
free(cp);
}
/*
* Output Framing field if present.
*/
if (fields[6].value) {
cp = msg;
if (framing == FRAMING_LLC_SNAP) {
msg = make_message("%s (%s)", cp, fields[6].value);
}
free(cp);
}
/*
* Output VLAN ID if the VLAN field is present.
*/
if (fields[7].value) {
cp = msg;
msg = make_message("%s (802.1Q VLAN=%s)", cp, fields[7].value);
free(cp);
}
/*
* Output Proto field if present.
*/
if (fields[8].value) {
cp = msg;
msg = make_message("%s (ARP Proto=%s)", cp, fields[8].value);
free(cp);
}
/*
* Output DUP field if present.
*/
if (fields[9].value) {
cp = msg;
msg = make_message("%s (DUP: %s)", cp, fields[9].value);
free(cp);
}
/*
* Output RTT field if present.
*/
if (fields[10].value) {
cp = msg;
msg = make_message("%s\tRTT=%s ms", cp, fields[10].value);
free(cp);
}
} else { /* --format option given */
format_element *fmt;
int idx;
msg = dupstr(""); /* Set msg to empty string */
for (fmt=format; fmt; fmt=fmt->next) {
if (fmt->type == FORMAT_FIELD) {
if ((idx=name_to_id(fmt->data, fields_map)) != -1 &&
fields[idx].value) {
cp = msg;
msg = make_message("%s%*s", cp, fmt->width, fields[idx].value);
free(cp);
} else { /* Field name not found in map */
warn_msg("WARNING: Field ${%s} unknown or not available",
fmt->data);
}
} else if (fmt->type == FORMAT_STRING) {
cp = msg;
msg = make_message("%s%s", cp, fmt->data);
free(cp);
}
}
}
/*
* Display the message on stdout and flush output buffer.
*/
printf("%s\n", msg);
fflush(stdout);
/*
* Free the message and any field values.
*/
free(msg);
for (i=0; iaddr.s_addr;
}
} else {
arpei.ar_sip = arp_spa;
}
if (he)
arpei.ar_tip = he->addr.s_addr;
/*
* Copy the required data into the output buffer "buf" and set "buflen"
* to the number of bytes in this buffer.
*/
marshal_arp_pkt(buf, &frame_hdr, &arpei, &buflen, padding, padding_len);
/*
* If host entry pointer is NULL, just return with the packet length.
*/
if (he == NULL)
return buflen;
/*
* Check that the host is live. Complain if not.
*/
if (!he->live) {
warn_msg("WARNING: Send attempt to inactive host: SHOULDN'T HAPPEN\n"
" Please report to github.com/royhills/arp-scan/issues");
return 0;
}
/*
* Update the last send times for this host.
*/
Gettimeofday(last_packet_time);
he->last_send_time.tv_sec = last_packet_time->tv_sec;
he->last_send_time.tv_usec = last_packet_time->tv_usec;
he->num_sent++;
/*
* If we are using the undocumented --readpktfromfile option, don't send
* anything and just return with the number of bytes we would have sent.
*/
if (pkt_read_file_flag) {
return buflen;
}
/*
* Send the packet.
*/
if (verbose > 1)
warn_msg("---\tSending packet #%u to host %s tmo %d", he->num_sent,
my_ntoa(he->addr), he->timeout);
if (write_pkt_to_file) { /* Writing to file */
nsent = write(write_pkt_to_file, buf, buflen);
} else { /* Send packet to Ethernet adaptor */
to.tv_sec = retry_send_interval/1000000;
to.tv_usec = (retry_send_interval - 1000000*to.tv_sec);
for (i=0; i= 0) { /* Successfully sent packet */
break;
} else if (errno != EAGAIN) { /* Unrecoverable error */
err_sys("ERROR: failed to send packet");
}
if (retry_send_interval > 0) {
if (verbose)
warn_msg("---\tRetrying send after %d microsecond delay (#%d of %d)",
retry_send_interval, i, retry_send);
n = select(0, NULL, NULL, NULL, &to); /* Delay */
if (n < 0) {
err_sys("select");
}
}
}
}
if (nsent < 0)
err_sys("ERROR: failed to send packet");
return buflen;
}
/*
* clean_up -- Protocol-specific Clean-Up routine.
*
* Inputs:
*
* None.
*
* Returns:
*
* None.
*
* This is called once after all hosts have been processed. It can be
* used to perform any tidying-up or statistics-displaying required.
*/
void
clean_up(pcap_t *pcap_handle) {
struct pcap_stat stats;
if (!plain_flag) {
if (pcap_handle && !pkt_read_file_flag) {
if ((pcap_stats(pcap_handle, &stats)) < 0)
err_msg("pcap_stats: %s", pcap_geterr(pcap_handle));
printf("%u packets received by filter, %u packets dropped by kernel\n",
stats.ps_recv, stats.ps_drop);
}
}
if (pcap_dump_handle) {
pcap_dump_close(pcap_dump_handle);
}
if (pcap_handle) {
pcap_close(pcap_handle);
}
}
/*
* usage -- display usage message and exit
*
* Inputs:
*
* None
*
* Returns:
*
* None (this function never returns).
*/
void
usage(void) {
printf("Usage: arp-scan [options] [hosts...]\n");
printf("\n");
printf("Target hosts must be specified on the command line unless the --file or\n");
printf("--localnet option is used.\n");
printf("\n");
printf("arp-scan uses raw sockets, which requires privileges on some systems:\n");
printf("\n");
printf("Linux with POSIX.1e capabilities support using libcap:\n");
printf(" arp-scan is capabilities aware. It requires CAP_NET_RAW in the permitted\n");
printf(" set and only enables that capability for the required functions.\n");
printf("BSD and macOS:\n");
printf(" You need read/write access to /dev/bpf*\n");
printf("Any operating system:\n");
printf(" Running as root or SUID root will work on any OS but other methods\n");
printf(" are preferable where possible.\n");
printf("\n");
printf("Targets can be IPv4 addresses or hostnames. You can also use CIDR notation\n");
printf("(e.g. 10.0.0.0/24), network:mask notation (e.g. 10.0.0.0:255.255.255.0) and IP\n");
printf("ranges (e.g. 10.0.0.1-10.0.0.10). CIDR and network:netmask notation include the\n");
printf("IP network and broadcast addresses by default unless the --exclude-broadcast\n");
printf("option is specified.\n");
printf("\n");
printf("Options:\n");
printf("\n");
printf("The data type for option arguments is shown by a letter in angle brackets: \n");
printf("\n");
printf(" Character string.\n");
printf(" Decimal integer, or hex if preceeded by 0x e.g. 2048 or 0x800.\n");
printf(" Floating point decimal number.\n");
printf(" MAC address, e.g. 01:23:45:67:89:ab or 01-23-45-67-89-ab (case insensitive)\n");
printf(" IPv4 address e.g. 10.0.0.1\n");
printf(" Hex encoded binary data. No leading 0x. (case insensitive).\n");
printf(" Something else - see option description.\n");
printf("\n");
printf("General Options:\n");
printf("\n--help or -h\t\tDisplay this usage message and exit.\n");
printf("\n--verbose or -v\t\tDisplay verbose progress messages.\n");
printf("\t\t\tCan be used than once to increase verbosity. Max=3.\n");
printf("\n--version or -V\t\tDisplay program version details and exit.\n");
printf("\t\t\tShows the version, license details, libpcap version,\n");
printf("\t\t\tand whether POSIX.1e capability support is included.\n");
printf("\n--interface= or -I Use network interface .\n");
printf("\t\t\tIf this option is not specified, arp-scan will search\n");
printf("\t\t\tthe system interface list for the lowest numbered,\n");
printf("\t\t\tconfigured up interface (excluding loopback).\n");
printf("\n");
printf("Host Selection:\n");
printf("\n--file= or -f \tRead hostnames or addresses from the specified file\n");
printf("\t\t\tOne name or address pattern per line. Use \"-\" for stdin.\n");
printf("\n--localnet or -l\tGenerate addresses from interface configuration.\n");
printf("\t\t\tGenerates list from interface address and netmask\n");
printf("\t\t\t(network and broadcast included unless the\n");
printf("\t\t\t--exclude-broadcast option is specified). You cannot use\n");
printf("\t\t\tthe --file option or give targets on the command line\n");
printf("\t\t\twith this option. Use the --interface option to specify\n");
printf("\t\t\tthe interface if you are not using the first adapter.\n");
printf("\n--exclude-broadcast\tExclude the IP network and broadcast addresses from\n");
printf("\t\t\tranges generated with network/bits and network:netmask\n");
printf("\t\t\taddress patterns as well as the range generated from the\n");
printf("\t\t\tinterface configuration with the --localnet option.\n");
printf("\n");
printf("MAC/Vendor Mapping Files:\n");
printf("\n--ouifile= or -O \tUse IEEE registry vendor mapping file .\n");
printf("\t\t\tDefault is %s in the current directory. If\n", OUIFILENAME);
printf("\t\t\tthat is not found %s/%s\n", PKGDATADIR, OUIFILENAME);
printf("\t\t\tis used.\n");
printf("\n--macfile= or -m \tUse custom vendor mapping file .\n");
printf("\t\t\tDefault is %s in the current directory.\n", MACFILENAME);
printf("\t\t\tIf that is not found\n");
printf("\t\t\t%s/%s is used.\n", PKGSYSCONFDIR, MACFILENAME);
printf("\n");
printf("Output Format Control:\n");
printf("\n--quiet or -q\t\tDisplay minimal output for each responding host.\n");
printf("\t\t\tOnly the IP address and MAC address are displayed.\n");
printf("\t\t\tReduces memory usage by about 5MB because the\n");
printf("\t\t\tvendor mapping files are not used. Only the ${ip}\n");
printf("\t\t\tand ${mac} fields are available for the --format\n");
printf("\t\t\toption if --quiet is specified.\n");
printf("\n--plain or -x\t\tSupress header and footer text.\n");
printf("\t\t\tOnly display the responding host details. Useful if\n");
printf("\t\t\tthe output will be parsed by a script.\n");
printf("\n--ignoredups or -g\tDon't display duplicate packets.\n");
printf("\t\t\tBy default duplicate packets are flagged with\n");
printf("\t\t\t\"(DUP: n)\" where n is the number of times this\n");
printf("\t\t\thost has responded.\n");
printf("\n--rtt or -D\t\tCalculate and display the packet round-trip time.\n");
printf("\t\t\tThe time is displayed in milliseconds and fractional\n");
printf("\t\t\tmicroseconds. Makes the ${rtt} field available for\n");
printf("\t\t\t--format.\n");
printf("\n--format= or -F \tSpecify the output format string.\n");
printf("\t\t\tThe format is a string that will be output for each\n");
printf("\t\t\tresponding host. Host details can be included by\n");
printf("\t\t\tinserting references to fields using the syntax\n");
printf("\t\t\t\"${field[;width]}\". Fields are displayed right-\n");
printf("\t\t\taligned unless the width is negative in which case\n");
printf("\t\t\tleft alignment will be used. The following case-\n");
printf("\t\t\tinsensitive field names are recognised:\n");
printf("\n");
printf("\t\t\tIP\tHost IPv4 address in dotted quad format\n");
printf("\t\t\tName\tHost name if --resolve option given\n");
printf("\t\t\tMAC\tHost MAC address xx:xx:xx:xx:xx:xx\n");
printf("\t\t\tHdrMAC\tEthernet source addr if different\n");
printf("\t\t\tVendor\tVendor details string\n");
printf("\t\t\tPadding\tPadding after ARP packet in hex if nonzero\n");
printf("\t\t\tFraming\tFraming type if not Ethernet_II\n");
printf("\t\t\tVLAN\t802.1Q VLAD ID if present\n");
printf("\t\t\tProto\tARP protocol if not 0x0800\n");
printf("\t\t\tDUP\tPacket number for duplicate packets (>1)\n");
printf("\t\t\tRTT\tRound trip time if --rtt option given\n");
printf("\t\t\tIPnum\tHost IPv4 address as a 32-bit integer\n");
printf("\t\t\t\n");
printf("\t\t\tOnly the \"ip\" and \"mac\" fields are available if the\n");
printf("\t\t\t--quiet option is specified.\n");
printf("\t\t\t\n");
printf("\t\t\tAny characters that are not fields are output\n");
printf("\t\t\tverbatim. \"\\\" introduces escapes:\n");
printf("\t\t\t\n");
printf("\t\t\t\\n newline\n");
printf("\t\t\t\\r carriage return\n");
printf("\t\t\t\\t tab\n");
printf("\t\t\t\\ suppress special meaning for following character\n");
printf("\t\t\t\n");
printf("\t\t\tYou should enclose the --format argument in 'single\n");
printf("\t\t\tquotes' to protect special characters from the shell.\n");
printf("\t\t\t\n");
printf("\t\t\tExample: --format='${ip}\\t${mac}\\t${vendor}'\n");
printf("\n");
printf("Host List Randomisation:\n");
printf("\n--random or -R\t\tRandomise the target host list.\n");
printf("\n--randomseed=\tSeed the pseudo random number generator.\n");
printf("\t\t\tUseful if you want a reproducible --random order.\n");
printf("\n");
printf("Output Timing and Retry:\n");
printf("\n--retry= or -r \tSet total number of attempts per host to ,\n");
printf("\t\t\tdefault=%d.\n", DEFAULT_RETRY);
printf("\n--backoff= or -b \tSet backoff factor to , default=%.2f.\n", DEFAULT_BACKOFF_FACTOR);
printf("\t\t\tMultiplies timeout by for each pass.\n");
printf("\n--timeout= or -t \tSet initial per host timeout to ms, default=%d.\n", DEFAULT_TIMEOUT);
printf("\t\t\tThis timeout is for the first packet sent to each host.\n");
printf("\t\t\tsubsequent timeouts are multiplied by the backoff\n");
printf("\t\t\tfactor which is set with --backoff.\n");
printf("\n--interval= or -i Set minimum packet interval to .\n");
printf("\t\t\tThis controls the outgoing bandwidth usage by limiting\n");
printf("\t\t\tthe packet rate. If you want to use up to a given\n");
printf("\t\t\tbandwidth it is easier to use the --bandwidth option\n");
printf("\t\t\tinstead. The interval is in milliseconds, or\n");
printf("\t\t\tmicroseconds if \"u\" is appended.\n");
printf("\n--bandwidth= or -B Set outbound bandwidth to , default=%d.\n", DEFAULT_BANDWIDTH);
printf("\t\t\tThe value is in bits per second. Append K for\n");
printf("\t\t\tkilobits or M for megabits (decimal multiples). You\n");
printf("\t\t\tcannot specify both --interval and --bandwidth.\n");
printf("\n");
printf("DNS Resolution:\n");
printf("\n--numeric or -N\t\tTargets must be IP addresses, not hostnames.\n");
printf("\t\t\tCan reduce startup time for large target lists.\n");
printf("\n--resolve or -d\t\tResolve responding addresses to hostnames.\n");
printf("\t\t\tThe default output format will display the hostname\n");
printf("\t\t\tinstead of the IPv4 address. This option makes the\n");
printf("\t\t\t${name} field available for the --format option.\n");
printf("\n");
printf("Output ARP Packet:\n");
printf("\n--arpsha= or -u \tSet the ARP source Ethernet address.\n");
printf("\t\t\tSets the 48-bit ar$sha field but does not change the\n");
printf("\t\t\thardware address in the frame header, see --srcaddr\n");
printf("\t\t\tfor how to change that address. Default is the\n");
printf("\t\t\tEthernet address of the outgoing interface.\n");
printf("\n--arptha= or -w \tSet the ARP target Ethernet address.\n");
printf("\t\t\tSets the 48-bit ar$tha field. The default is zero\n");
printf("\t\t\tbecause this field is not used for ARP request packets.\n");
printf("\n--arphrd= or -H \tSet the ARP hardware type, default=%d.\n", DEFAULT_ARP_HRD);
printf("\t\t\tSets the 16-bit ar$hrd field. The default is 1\n");
printf("\t\t\t(ARPHRD_ETHER). Many operating systems also respond to\n");
printf("\t\t\t6 (ARPHRD_IEEE802)\n");
printf("\n--arppro= or -p \tSet the ARP protocol type, default=0x%.4x.\n", DEFAULT_ARP_PRO);
printf("\t\t\tSets the 16-bit ar$pro field. Most operating systems\n");
printf("\t\t\tonly respond to 0x0800 (IPv4).\n");
printf("\n--arphln= or -a \tSet the hardware address length, default=%d.\n", DEFAULT_ARP_HLN);
printf("\t\t\tSets the 8-bit ar$hln field. The lengths of the\n");
printf("\t\t\tar$sha and ar$tha fields are not changed by this\n");
printf("\t\t\toption; it only changes the ar$hln field.\n");
printf("\n--arppln= or -P \tSet the protocol address length, default=%d.\n", DEFAULT_ARP_PLN);
printf("\t\t\tSets the 8-bit ar$pln field. The lengths of the ar$spa\n");
printf("\t\t\tand ar$tpa fields are not changed by this option;\n");
printf("\t\t\tit only changes the ar$pln field.\n");
printf("\n--arpop= or -o \tSpecify the ARP operation, default=%d.\n", DEFAULT_ARP_OP);
printf("\t\t\tSets the 16-bit ar$op field. Most operating systems\n");
printf("\t\t\tonly respond to the value 1 (ARPOP_REQUEST).\n");
printf("\n--arpspa= or -s \tSet the source IPv4 address.\n");
printf("\t\t\tThe address should be in dotted quad format, or the\n");
printf("\t\t\tstring \"dest\", which sets the source address to\n");
printf("\t\t\tthe target host address. The default is the outgoing\n");
printf("\t\t\tinterface address. Sets the 32-bit ar$spa field. Some\n");
printf("\t\t\toperating systems only respond if the source address\n");
printf("\t\t\tis within the network of the receiving interface.\n");
printf("\t\t\tSetting ar$spa to the destination IP address can cause\n");
printf("\t\t\tsome operating systems to report an address clash.\n");
printf("\n");
printf("Output Ethernet Header:\n");
printf("\n--srcaddr= or -S Set the source Ethernet MAC address.\n");
printf("\t\t\tDefault is the interface MAC address. This sets the\n");
printf("\t\t\taddress in the Ethernet header. It does not change the\n");
printf("\t\t\taddress in the ARP packet: use --arpsha to change\n");
printf("\t\t\tthat address.\n");
printf("\n--destaddr= or -T Set the destination MAC address.\n");
printf("\t\t\tSets the destination address in the Ethernet\n");
printf("\t\t\theader. Default is ff:ff:ff:ff:ff:ff (broadcast)\n");
printf("\t\t\tHosts also respond if the request is sent to their\n");
printf("\t\t\tunicast address, or to a multicast address they\n");
printf("\t\t\tare listening on.\n");
printf("\n--prototype= or -y Sets the Ethernet protocol type, default=0x%.4x.\n", DEFAULT_ETH_PRO);
printf("\t\t\tThis sets the protocol type field in the Ethernet\n");
printf("\t\t\theader.\n");
printf("\n--llc or -L\t\tUse RFC 1042 LLC/SNAP encapsulation for 802.2 networks.\n");
printf("\t\t\tarp-scan will decode and display ARP responses in both\n");
printf("\t\t\tEthernet-II and IEEE 802.2 formats irrespective of\n");
printf("\t\t\tthis option.\n");
printf("\n--vlan= or -Q \tUse 802.1Q tagging with VLAN id .\n");
printf("\t\t\tThe id should be in the range 0 to 4095. arp-scan will\n");
printf("\t\t\tdecode and display ARP responses in 802.1Q format\n");
printf("\t\t\tirrespective of this option.\n");
printf("\n");
printf("Misc Options:\n");
printf("\n--limit= or -M \tExit after the specified number of hosts have responded.\n");
printf("\t\t\tarp-scan will exit with status 1 if the number of\n");
printf("\t\t\tresponding hosts is less than the limit. Can be used\n");
printf("\t\t\tin scripts to check if fewer hosts respond without\n");
printf("\t\t\thaving to parse the output.\n");
printf("\n--pcapsavefile= or -W \tWrite received packets to pcap savefile .\n");
printf("\t\t\tARP responses will be written to the specified file\n");
printf("\t\t\tas well as being decoded and displayed.\n");
printf("\n--snap= or -n \tSet the pcap snap length to . Default=%d.\n", SNAPLEN);
printf("\t\t\tSpecifies the frame capture length, including the\n");
printf("\t\t\tEthernet header. The default is normally sufficient.\n");
printf("\n--retry-send= or -Y Set number of send attempts, default=%d.\n", DEFAULT_RETRY_SEND);
printf("\n--retry-send-interval= or -E Set interval between send attempts.\n");
printf("\t\t\tInterval is in milliseconds or microseconds if \"u\"\n");
printf("\t\t\tis appended. default=%d.\n", DEFAULT_RETRY_SEND_INTERVAL/1000);
printf("\n--padding= or -A \tSpecify padding after packet data.\n");
printf("\t\t\tSet padding after the ARP request to hex value .\n");
printf("\n");
printf("Report bugs or send suggestions at %s\n", PACKAGE_BUGREPORT);
printf("See the arp-scan homepage at https://github.com/royhills/arp-scan\n");
exit(EXIT_SUCCESS);
}
/*
* add_host_pattern -- Add one or more new hosts to the list.
*
* Inputs:
*
* pattern = The host pattern to add.
* host_timeout = Per-host timeout in ms.
*
* Returns: None
*
* This adds one or more new hosts to the list. The pattern argument
* can either be a single host or IP address, in which case one host
* will be added to the list, or it can specify a number of hosts with
* the IPnet/bits or IPstart-IPend formats.
*
* The host_timeout and num_hosts arguments are passed unchanged to
* add_host().
*/
void
add_host_pattern(const char *pattern, unsigned host_timeout) {
char *patcopy;
struct in_addr in_val;
struct in_addr mask_val;
unsigned numbits;
char *cp;
uint32_t ipnet_val;
uint32_t network;
uint32_t mask;
unsigned long hoststart;
unsigned long hostend;
unsigned i;
uint32_t x;
static int first_call = 1;
static regex_t iprange_pat;
static regex_t ipslash_pat;
static regex_t ipmask_pat;
static const char *iprange_pat_str =
"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+-[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+";
static const char *ipslash_pat_str =
"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+/[0-9]+";
static const char *ipmask_pat_str =
"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+";
/*
* Compile regex patterns if this is the first time we've been called.
*/
if (first_call) {
int result;
char *errbuf;
size_t size;
first_call = 0;
if ((result = regcomp(&iprange_pat, iprange_pat_str,
REG_EXTENDED|REG_NOSUB))) {
size = regerror(result, &iprange_pat, NULL, 0);
errbuf = Malloc(size);
regerror(result, &iprange_pat, errbuf, size);
err_msg("ERROR: cannot compile regex pattern \"%s\": %s",
iprange_pat_str, errbuf);
}
if ((result = regcomp(&ipslash_pat, ipslash_pat_str,
REG_EXTENDED|REG_NOSUB))) {
size = regerror(result, &ipslash_pat, NULL, 0);
errbuf = Malloc(size);
regerror(result, &ipslash_pat, errbuf, size);
err_msg("ERROR: cannot compile regex pattern \"%s\": %s",
ipslash_pat_str, errbuf);
}
if ((result = regcomp(&ipmask_pat, ipmask_pat_str,
REG_EXTENDED|REG_NOSUB))) {
size = regerror(result, &ipmask_pat, NULL, 0);
errbuf = Malloc(size);
regerror(result, &ipmask_pat, errbuf, size);
err_msg("ERROR: cannot compile regex pattern \"%s\": %s",
ipmask_pat_str, errbuf);
}
}
/*
* Make a copy of pattern because we don't want to modify our argument.
*/
patcopy = dupstr(pattern);
if (!(regexec(&ipslash_pat, patcopy, 0, NULL, 0))) { /* IPnet/bits */
/*
* Get IPnet and bits as integers. Perform basic error checking.
*/
cp = strchr(patcopy, '/');
*(cp++) = '\0'; /* patcopy points to IPnet, cp points to bits */
if (!(inet_aton(patcopy, &in_val)))
err_msg("ERROR: %s is not a valid IPv4 network", patcopy);
ipnet_val = ntohl(in_val.s_addr); /* We need host byte order */
numbits = Strtoul(cp, 10);
if (numbits<3 || numbits>32)
err_msg("ERROR: Number of bits in %s must be between 3 and 32",
pattern);
/*
* Construct 32-bit network bitmask from number of bits.
*/
mask = 0;
for (i=0; i> 24;
b2 = (hostip & 0x00ff0000) >> 16;
b3 = (hostip & 0x0000ff00) >> 8;
b4 = (hostip & 0x000000ff);
snprintf(ipstr, sizeof(ipstr), "%d.%d.%d.%d", b1, b2, b3, b4);
add_host(ipstr, host_timeout, 1);
}
} else if (!(regexec(&ipmask_pat, patcopy, 0, NULL, 0))) { /* IPnet:netmask */
/*
* Get IPnet and bits as integers. Perform basic error checking.
*/
cp = strchr(patcopy, ':');
*(cp++) = '\0'; /* patcopy points to IPnet, cp points to netmask */
if (!(inet_aton(patcopy, &in_val)))
err_msg("ERROR: %s is not a valid IPv4 network", patcopy);
ipnet_val = ntohl(in_val.s_addr); /* We need host byte order */
if (!(inet_aton(cp, &mask_val)))
err_msg("ERROR: %s is not a valid netmask", cp);
mask = ntohl(mask_val.s_addr); /* We need host byte order */
/*
* Calculate the number of bits in the network.
*/
x = mask;
for (numbits=0; x != 0; x>>=1) {
if (x & 0x01) {
numbits++;
}
}
/*
* Mask off the network. Warn if the host bits were non-zero.
*/
network = ipnet_val & mask;
if (network != ipnet_val)
warn_msg("WARNING: host part of %s is non-zero", pattern);
/*
* Determine maximum and minimum host values including the network
* and broadcast addresses.
*/
hoststart = exclude_broadcast==1 ? 1 : 0;
hostend = (1<<(32-numbits)) - (exclude_broadcast==1 ? 2 : 1);
/*
* Calculate all host addresses in the range and feed to add_host()
* in dotted-quad format.
*/
for (i=hoststart; i<=hostend; i++) {
uint32_t hostip;
int b1, b2, b3, b4;
char ipstr[16];
hostip = network+i;
b1 = (hostip & 0xff000000) >> 24;
b2 = (hostip & 0x00ff0000) >> 16;
b3 = (hostip & 0x0000ff00) >> 8;
b4 = (hostip & 0x000000ff);
snprintf(ipstr, sizeof(ipstr), "%d.%d.%d.%d", b1, b2, b3, b4);
add_host(ipstr, host_timeout, 1);
}
} else if (!(regexec(&iprange_pat, patcopy, 0, NULL, 0))) { /* IPstart-IPend */
/*
* Get IPstart and IPend as integers.
*/
cp = strchr(patcopy, '-');
*(cp++) = '\0'; /* patcopy points to IPstart, cp points to IPend */
if (!(inet_aton(patcopy, &in_val)))
err_msg("ERROR: Invalid range specification: %s is not a valid IPv4 "
"address", patcopy);
hoststart = ntohl(in_val.s_addr); /* We need host byte order */
if (!(inet_aton(cp, &in_val)))
err_msg("ERROR: Invalid range specification: %s is not a valid IPv4 "
"address", cp);
hostend = ntohl(in_val.s_addr); /* We need host byte order */
/*
* Calculate all host addresses in the range and feed to add_host()
* in dotted-quad format.
*/
for (i=hoststart; i<=hostend; i++) {
int b1, b2, b3, b4;
char ipstr[16];
b1 = (i & 0xff000000) >> 24;
b2 = (i & 0x00ff0000) >> 16;
b3 = (i & 0x0000ff00) >> 8;
b4 = (i & 0x000000ff);
snprintf(ipstr, sizeof(ipstr), "%d.%d.%d.%d", b1, b2, b3, b4);
add_host(ipstr, host_timeout, 1);
}
} else { /* Single host or IP address */
add_host(patcopy, host_timeout, numeric_flag);
}
free(patcopy);
}
/*
* add_host -- Add a new host to the list.
*
* Inputs:
*
* host_name = The Name or IP address of the host.
* host_timeout = The initial host timeout in ms.
* numeric_only = 1 if the host name is definitely an IP address in
* dotted quad format, or 0 if it may be a hostname or
* IP address.
*
* Returns:
*
* None.
*
* This function is called before the helistptr array is created, so
* we use the helist array directly.
*/
void
add_host(const char *host_name, unsigned host_timeout, int numeric_only) {
struct in_addr *hp = NULL;
struct in_addr addr;
host_entry *he;
static int num_left = 0; /* Number of free entries left */
int result;
char *ga_err_msg;
if (numeric_only) {
result = inet_pton(AF_INET, host_name, &addr);
if (result < 0) {
err_sys("ERROR: inet_pton failed for %s", host_name);
} else if (result == 0) {
warn_msg("WARNING: \"%s\" is not a valid IPv4 address - target ignored", host_name);
return;
}
} else {
hp = get_host_address(host_name, &addr, &ga_err_msg);
if (hp == NULL) {
warn_msg("WARNING: get_host_address failed for \"%s\": %s - target ignored",
host_name, ga_err_msg);
return;
}
}
if (!num_left) { /* No entries left, allocate some more */
if (helist)
helist = Realloc(helist, (num_hosts * sizeof(host_entry)) +
REALLOC_COUNT*sizeof(host_entry));
else
helist = Malloc(REALLOC_COUNT*sizeof(host_entry));
num_left = REALLOC_COUNT;
}
he = helist + num_hosts; /* Would array notation be better? */
num_hosts++;
num_left--;
memcpy(&(he->addr), &addr, sizeof(struct in_addr));
he->live = 1;
he->timeout = host_timeout * 1000; /* Convert from ms to us */
he->num_sent = 0;
he->num_recv = 0;
he->last_send_time.tv_sec = 0;
he->last_send_time.tv_usec = 0;
}
/*
* remove_host -- Remove the specified host from the list
*
* inputs:
*
* he = Pointer to host entry to remove.
*
* Returns:
*
* None.
*
* If the host being removed is the one pointed to by the cursor, this
* function updates cursor so that it points to the next entry.
*/
void
remove_host(host_entry **he) {
if ((*he)->live) {
(*he)->live = 0;
live_count--;
if (*he == *cursor)
advance_cursor();
} else {
warn_msg("WARNING: Attempt to remove inactive host: SHOULDN'T HAPPEN\n"
" Please report to github.com/royhills/arp-scan/issues");
}
}
/*
* advance_cursor -- Advance the cursor to point at next live entry
*
* Inputs:
*
* None.
*
* Returns:
*
* None.
*
* Does nothing if there are no live entries in the list.
*/
void
advance_cursor(void) {
if (live_count) {
do {
if (cursor == (helistptr+(num_hosts-1)))
cursor = helistptr; /* Wrap round to beginning */
else
cursor++;
} while (!(*cursor)->live);
} /* End If */
}
/*
* find_host -- Find a host in the list
*
* Inputs:
*
* he Pointer to the current position in the list. Search runs
* backwards starting from this point.
* addr The source IP address that the packet came from.
*
* Returns a pointer to the host entry associated with the specified IP
* or NULL if no match found.
*
* This routine finds the host by IP address by comparing "addr" against
* "he->addr" for each entry in the list.
*/
host_entry *
find_host(host_entry **he, struct in_addr *addr) {
host_entry **p;
int found = 0;
assert (*he != NULL);
/*
* Try to match against our host list.
*/
p = he;
do {
if ((*p)->addr.s_addr == addr->s_addr) {
found = 1;
} else {
if (p == helistptr) {
p = helistptr + (num_hosts-1); /* Wrap round to end */
} else {
p--;
}
}
} while (!found && p != he);
if (found)
return *p;
else
return NULL;
}
/*
* recvfrom_wto -- Receive packet with timeout
*
* Inputs:
*
* sock_fd = Socket file descriptor.
* tmo = Select timeout in us.
* pcap_handle = pcap handle
*
* Returns:
*
* None.
*
* If the socket file descriptor is -1, this indicates that we are
* reading packets from a pcap file and there is no associated network
* device.
*/
void
recvfrom_wto(int sock_fd, int tmo, pcap_t *pcap_handle) {
fd_set readset;
struct timeval to;
int n;
FD_ZERO(&readset);
if (sock_fd >= 0)
FD_SET(sock_fd, &readset);
to.tv_sec = tmo/1000000;
to.tv_usec = (tmo - 1000000*to.tv_sec);
n = select(sock_fd+1, &readset, NULL, NULL, &to);
if (n < 0) {
err_sys("select");
} else if (n == 0 && sock_fd >= 0) {
return; /* Timeout */
}
/*
* Call pcap_dispatch() to process the packet if we are reading packets.
*/
if (pcap_handle) {
if ((pcap_dispatch(pcap_handle, -1, callback, NULL)) == -1)
err_sys("pcap_dispatch: %s\n", pcap_geterr(pcap_handle));
}
}
/*
* dump_list -- Display contents of host list for debugging
*
* Inputs:
*
* None.
*
* Returns:
*
* None.
*/
void
dump_list(void) {
unsigned i;
printf("Host List:\n\n");
printf("Entry\tIP Address\n");
for (i=0; iaddr));
printf("\nTotal of %u host entries.\n\n", num_hosts);
}
/*
* callback -- pcap callback function
*
* Inputs:
*
* args Special args (not used)
* header pcap header structure
* packet_in The captured packet
*
* Returns:
*
* None
*/
void
callback(u_char *args ATTRIBUTE_UNUSED,
const struct pcap_pkthdr *header, const u_char *packet_in) {
arp_ether_ipv4 arpei;
ether_hdr frame_hdr;
int n = header->caplen;
struct in_addr source_ip;
host_entry *temp_cursor;
unsigned char extra_data[MAX_FRAME];
size_t extra_data_len;
int vlan_id;
int framing;
/*
* Check that the packet is large enough to decode.
*/
if (n < ETHER_HDR_SIZE + ARP_PKT_SIZE) {
warn_msg("WARNING: %d byte packet too short to decode.", n);
return;
}
/*
* Limit packet size to the maximum Ethernet frame size we expect
* to avoid potential buffer overruns later.
*/
if (n > MAX_FRAME) {
n = MAX_FRAME;
}
/*
* Unmarshal packet buffer into structures and determine framing type
*/
framing = unmarshal_arp_pkt(packet_in, n, &frame_hdr, &arpei, extra_data,
&extra_data_len, &vlan_id);
/*
* Determine source IP address.
*/
source_ip.s_addr = arpei.ar_sip;
/*
* Try to match up the packet by IP address
*
* We should really start searching at the host before the cursor, as we
* know that the host to match cannot be the one at the cursor position
* because we call advance_cursor() after sending each packet. However,
* the time saved is minimal, and it's not worth the extra complexity.
*/
temp_cursor = find_host(cursor, &source_ip);
if (temp_cursor) {
/*
* We found an IP match for the packet.
* Increment number of responses received for this host and increment
* total number of responding hosts if this is the first response for
* this host (i.e. it is not a duplicate response).
*/
temp_cursor->num_recv++;
if (temp_cursor->num_recv == 1)
responders++; /* Increment responders if not a dup response */
if (verbose > 1)
warn_msg("---\tReceived packet #%u from %s",
temp_cursor->num_recv, my_ntoa(source_ip));
/*
* Display the packet if this is the first response for this host
* or if we are not ignoring duplicates.
*/
if ((temp_cursor->num_recv == 1 || !ignore_dups)) {
if (pcap_dump_handle) {
pcap_dump((unsigned char *)pcap_dump_handle, header, packet_in);
}
display_packet(temp_cursor, &arpei, extra_data, extra_data_len,
framing, vlan_id, &frame_hdr, header);
}
if (verbose > 1)
warn_msg("---\tRemoving host %s - Received %d bytes",
my_ntoa(source_ip), n);
/*
* Remove the responding host from the list if it is marked as "live".
*/
if (temp_cursor->live)
remove_host(&temp_cursor);
} else {
/*
* The received packet is not from an IP address in the list
* Issue a message to that effect and ignore the packet.
*/
if (verbose)
warn_msg("---\tIgnoring %d bytes from unknown host %s", n,
my_ntoa(source_ip));
}
}
/*
* process_options -- Process options and arguments.
*
* Inputs:
*
* argc Command line arg count
* argv Command line args
*
* Returns:
*
* None.
*/
void
process_options(int argc, char *argv[]) {
struct option long_options[] = {
{"file", required_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"retry", required_argument, 0, 'r'},
{"retry-send", required_argument, 0, 'Y'},
{"retry-send-interval", required_argument, 0, 'E'},
{"timeout", required_argument, 0, 't'},
{"interval", required_argument, 0, 'i'},
{"backoff", required_argument, 0, 'b'},
{"verbose", no_argument, 0, 'v'},
{"version", no_argument, 0, 'V'},
{"snap", required_argument, 0, 'n'},
{"interface", required_argument, 0, 'I'},
{"quiet", no_argument, 0, 'q'},
{"ignoredups", no_argument, 0, 'g'},
{"random", no_argument, 0, 'R'},
{"numeric", no_argument, 0, 'N'},
{"bandwidth", required_argument, 0, 'B'},
{"ouifile", required_argument, 0, 'O'},
{"macfile", required_argument, 0, 'm'},
{"arpspa", required_argument, 0, 's'},
{"arpop", required_argument, 0, 'o'},
{"arphrd", required_argument, 0, 'H'},
{"arppro", required_argument, 0, 'p'},
{"destaddr", required_argument, 0, 'T'},
{"arppln", required_argument, 0, 'P'},
{"arphln", required_argument, 0, 'a'},
{"padding", required_argument, 0, 'A'},
{"prototype", required_argument, 0, 'y'},
{"arpsha", required_argument, 0, 'u'},
{"arptha", required_argument, 0, 'w'},
{"srcaddr", required_argument, 0, 'S'},
{"localnet", no_argument, 0, 'l'},
{"llc", no_argument, 0, 'L'},
{"vlan", required_argument, 0, 'Q'},
{"pcapsavefile", required_argument, 0, 'W'},
{"writepkttofile", required_argument, 0, OPT_WRITEPKTTOFILE},
{"readpktfromfile", required_argument, 0, OPT_READPKTFROMFILE},
{"rtt", no_argument, 0, 'D'},
{"plain", no_argument, 0, 'x'},
{"randomseed", required_argument, 0, OPT_RANDOMSEED},
{"limit", required_argument, 0, 'M'},
{"resolve", no_argument, 0, 'd'},
{"format", required_argument, 0, 'F'},
{"exclude-broadcast", no_argument, 0, OPT_EXCLUDEBROADCAST},
{0, 0, 0, 0}
};
/*
* available short option characters:
*
* lower: --c-e----jk--------------z
* UPPER: --C---G--JK---------U--X-Z
* Digits: 0123456789
*/
const char *short_options =
"f:hr:Y:E:t:i:b:vVn:I:qgRNB:O:s:o:H:p:T:P:a:A:y:u:w:S:F:m:lLQ:W:DxM:dk:";
int arg;
int options_index = 0;
while ((arg = getopt_long_only(argc, argv, short_options, long_options,
&options_index)) != -1) {
switch (arg) {
struct in_addr source_ip_address;
int result;
case 'f': /* --file */
filename = make_message("%s", optarg);
filename_flag = 1;
break;
case 'h': /* --help */
usage();
break; /* NOTREACHED */
case 'r': /* --retry */
retry = Strtoul(optarg, 10);
break;
case 'Y': /* --retry-send */
retry_send = Strtoul(optarg, 10);
break;
case 'E': /* --retry-send-interval */
retry_send_interval = str_to_interval(optarg);
break;
case 't': /* --timeout */
timeout = Strtoul(optarg, 10);
break;
case 'i': /* --interval */
interval = str_to_interval(optarg);
break;
case 'b': /* --backoff */
backoff_factor = atof(optarg);
break;
case 'v': /* --verbose */
verbose++;
break;
case 'V': /* --version */
arp_scan_version();
exit(EXIT_SUCCESS);
case 'n': /* --snap */
snaplen = Strtol(optarg, 0);
break;
case 'I': /* --interface */
if_name = make_message("%s", optarg);
break;
case 'q': /* --quiet */
quiet_flag = 1;
break;
case 'g': /* --ignoredups */
ignore_dups = 1;
break;
case 'R': /* --random */
random_flag = 1;
break;
case 'N': /* --numeric */
numeric_flag = 1;
break;
case 'B': /* --bandwidth */
bandwidth = str_to_bandwidth(optarg);
break;
case 'O': /* --ouifile */
ouifilename = make_message("%s", optarg);
break;
case 'm': /* --macfile */
macfilename = make_message("%s", optarg);
break;
case 's': /* --arpspa */
arp_spa_flag = 1;
if ((strcmp(optarg, "dest")) == 0) {
arp_spa_is_tpa = 1;
} else {
if ((inet_pton(AF_INET, optarg, &source_ip_address)) <= 0)
err_msg("ERROR: Invalid IPv4 address: %s", optarg);
memcpy(&arp_spa, &(source_ip_address.s_addr), sizeof(arp_spa));
}
break;
case 'o': /* --arpop */
arp_op = Strtol(optarg, 0);
break;
case 'H': /* --arphrd */
arp_hrd = Strtol(optarg, 0);
break;
case 'p': /* --arppro */
arp_pro = Strtol(optarg, 0);
break;
case 'T': /* --destaddr */
result = get_ether_addr(optarg, target_mac);
if (result != 0)
err_msg("Invalid MAC address: %s", optarg);
break;
case 'P': /* --arppln */
arp_pln = Strtol(optarg, 0);
break;
case 'a': /* --arphln */
arp_hln = Strtol(optarg, 0);
break;
case 'A': /* --padding */
if (strlen(optarg) % 2) /* Length is odd */
err_msg("ERROR: Length of --padding argument must be even (multiple of 2).");
padding = hex2data(optarg, &padding_len);
break;
case 'y': /* --prototype */
eth_pro = Strtol(optarg, 0);
break;
case 'u': /* --arpsha */
result = get_ether_addr(optarg, arp_sha);
if (result != 0)
err_msg("Invalid MAC address: %s", optarg);
arp_sha_flag = 1;
break;
case 'w': /* --arptha */
result = get_ether_addr(optarg, arp_tha);
if (result != 0)
err_msg("Invalid MAC address: %s", optarg);
break;
case 'S': /* --srcaddr */
result = get_ether_addr(optarg, source_mac);
if (result != 0)
err_msg("Invalid MAC address: %s", optarg);
source_mac_flag = 1;
break;
case 'l': /* --localnet */
localnet_flag = 1;
break;
case 'L': /* --llc */
llc_flag = 1;
break;
case 'Q': /* --vlan */
ieee_8021q_vlan = Strtol(optarg, 0);
break;
case 'W': /* --pcapsavefile */
pcap_savefile = make_message("%s", optarg);
break;
case OPT_WRITEPKTTOFILE: /* --writepkttofile */
pkt_filename = make_message("%s", optarg);
pkt_write_file_flag = 1;
break;
case OPT_READPKTFROMFILE: /* --readpktfromfile */
pkt_filename = make_message("%s", optarg);
pkt_read_file_flag = 1;
break;
case 'D': /* --rtt */
rtt_flag = 1;
break;
case 'x': /* --plain */
plain_flag = 1;
break;
case OPT_RANDOMSEED: /* --randomseed */
random_seed = Strtoul(optarg, 0);
break;
case 'M': /* --limit */
host_limit = Strtoul(optarg, 10);
break;
case 'd': /* --resolve */
resolve_flag = 1;
break;
case OPT_EXCLUDEBROADCAST: /* --exclude-broadcast */
exclude_broadcast = 1;
break;
case 'F': /* --format */
format = format_parse(optarg);
break;
default: /* Unknown option */
err_msg("Usage: arp-scan [options] [hosts...]\n"
"Use \"arp-scan --help\" for detailed information on "
"the available options."); /* Never returns */
}
}
}
/*
* arp_scan_version -- display version information
*
* Inputs:
*
* None.
*
* Returns:
*
* None.
*
* This displays the arp-scan version information.
*/
void
arp_scan_version(void) {
printf("%s\n\n", PACKAGE_STRING);
printf("Copyright (C) 2005-2025 Roy Hills\n");
printf("License GPLv3+: GNU GPL version 3 or later \n");
printf("This is free software: you are free to change and redistribute it.\n");
printf("There is NO WARRANTY, to the extent permitted by law.\n");
printf("\n");
printf("%s\n", pcap_lib_version());
#ifdef HAVE_LIBCAP
printf("Built with libcap POSIX.1e capability support.\n");
#endif
#ifdef HAVE_PLEDGE
printf("Built with OpenBSD pledge(2) support.\n");
#endif
}
/*
* get_host_address -- Obtain target host IP address
*
* Inputs:
*
* name The name to lookup
* addr Pointer to the IP address buffer
* error_msg The error message, or NULL if no problem.
*
* Returns:
*
* Pointer to the IP address, or NULL if an error occurred.
*
* This function is basically a wrapper for getaddrinfo().
*/
struct in_addr *
get_host_address(const char *name, struct in_addr *addr, char **error_msg) {
static char err[MAXLINE];
struct addrinfo *res;
struct addrinfo hints;
struct sockaddr_in sa_in;
int result;
assert (addr != NULL);
memset(&hints, '\0', sizeof(hints));
hints.ai_family = AF_INET;
result = getaddrinfo(name, NULL, &hints, &res);
if (result != 0) { /* Error occurred */
snprintf(err, MAXLINE, "%s", gai_strerror(result));
*error_msg = err;
return NULL;
}
memcpy(&sa_in, res->ai_addr, sizeof(sa_in));
memcpy(addr, &sa_in.sin_addr, sizeof(struct in_addr));
freeaddrinfo(res);
*error_msg = NULL;
return addr;
}
/*
* get_host_name -- Obtain target host name from IP address
*
* Inputs:
*
* addr The IP address to lookup
* name Pointer to the name buffer
* error_msg The error message, or NULL if no problem.
*
* Returns:
*
* Pointer to the host name, or NULL if an error occurred.
*
* This function is basically a wrapper for getnameinfo().
*/
char *
get_host_name(const struct in_addr addr, char **error_msg) {
static char err[MAXLINE];
static char name[MAXLINE];
struct sockaddr_in sa_in;
int result;
sa_in.sin_family = AF_INET;
sa_in.sin_addr = addr;
result = getnameinfo((struct sockaddr *)&sa_in, sizeof(sa_in), name,
MAXLINE, NULL, 0, 0);
if (result != 0) { /* Error occurred */
snprintf(err, MAXLINE, "%s", gai_strerror(result));
*error_msg = err;
return NULL;
}
*error_msg = NULL;
return name;
}
/*
* my_ntoa -- inet_ntoa replacement
*
* Inputs:
*
* addr The IP address
*
* Returns:
*
* Pointer to the string representation of the IP address.
*
* This only supports IPv4.
*/
const char *
my_ntoa(struct in_addr addr) {
static char ip_str[MAXLINE];
const char *cp;
cp = inet_ntop(AF_INET, &addr, ip_str, MAXLINE);
return cp;
}
/*
* marshal_arp_pkt -- Marshal ARP packet from struct to buffer
*
* Inputs:
*
* buffer Pointer to the output buffer
* frame_hdr The Ethernet frame header
* arp_pkt The ARP packet
* buf_siz The size of the output buffer
* frame_padding Any padding to add after the ARP payload.
* frame_padding_len The length of the padding.
*
* Returns:
*
* None
*/
void
marshal_arp_pkt(unsigned char *buffer, ether_hdr *frame_hdr,
arp_ether_ipv4 *arp_pkt, size_t *buf_siz,
const unsigned char *frame_padding, size_t frame_padding_len) {
unsigned char llc_snap[] = {0xAA, 0xAA, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char vlan_tag[] = {0x81, 0x00, 0x00, 0x00};
unsigned char *cp;
size_t packet_size;
cp = buffer;
/*
* Set initial packet length to the size of an Ethernet frame using
* Ethernet-II format plus the size of the ARP data. This may be
* increased later by LLC/SNAP frame format or padding after the
* ARP data.
*/
packet_size = sizeof(frame_hdr->dest_addr) + sizeof(frame_hdr->src_addr) +
sizeof(frame_hdr->frame_type) +
sizeof(arp_pkt->ar_hrd) + sizeof(arp_pkt->ar_pro) +
sizeof(arp_pkt->ar_hln) + sizeof(arp_pkt->ar_pln) +
sizeof(arp_pkt->ar_op) + sizeof(arp_pkt->ar_sha) +
sizeof(arp_pkt->ar_sip) + sizeof(arp_pkt->ar_tha) +
sizeof(arp_pkt->ar_tip);
/*
* Copy the Ethernet frame header to the buffer.
*/
memcpy(cp, &(frame_hdr->dest_addr), sizeof(frame_hdr->dest_addr));
cp += sizeof(frame_hdr->dest_addr);
memcpy(cp, &(frame_hdr->src_addr), sizeof(frame_hdr->src_addr));
cp += sizeof(frame_hdr->src_addr);
/*
* Add 802.1Q tag if we are using VLAN tagging
*/
if (ieee_8021q_vlan != -1) {
uint16_t tci;
tci = htons(ieee_8021q_vlan);
memcpy(cp, vlan_tag, sizeof(vlan_tag));
memcpy(cp+2, &tci, sizeof(tci));
cp += sizeof(vlan_tag);
packet_size += sizeof(vlan_tag);
}
/*
* Add EtherType / Size field
*/
if (llc_flag) { /* With 802.2 LLC framing, type field is frame size */
uint16_t frame_size;
frame_size = htons(packet_size + sizeof(llc_snap));
memcpy(cp, &(frame_size), sizeof(frame_size));
} else { /* With Ethernet-II framing, type field is ether type */
memcpy(cp, &(frame_hdr->frame_type), sizeof(frame_hdr->frame_type));
}
cp += sizeof(frame_hdr->frame_type);
/*
* Add IEEE 802.2 LLC and SNAP fields if we are using LLC frame format.
*/
if (llc_flag) {
memcpy(cp, llc_snap, sizeof(llc_snap));
memcpy(cp+6, &(frame_hdr->frame_type), sizeof(frame_hdr->frame_type));
cp += sizeof(llc_snap);
packet_size += sizeof(llc_snap);
}
/*
* Add the ARP data.
*/
memcpy(cp, &(arp_pkt->ar_hrd), sizeof(arp_pkt->ar_hrd));
cp += sizeof(arp_pkt->ar_hrd);
memcpy(cp, &(arp_pkt->ar_pro), sizeof(arp_pkt->ar_pro));
cp += sizeof(arp_pkt->ar_pro);
memcpy(cp, &(arp_pkt->ar_hln), sizeof(arp_pkt->ar_hln));
cp += sizeof(arp_pkt->ar_hln);
memcpy(cp, &(arp_pkt->ar_pln), sizeof(arp_pkt->ar_pln));
cp += sizeof(arp_pkt->ar_pln);
memcpy(cp, &(arp_pkt->ar_op), sizeof(arp_pkt->ar_op));
cp += sizeof(arp_pkt->ar_op);
memcpy(cp, &(arp_pkt->ar_sha), sizeof(arp_pkt->ar_sha));
cp += sizeof(arp_pkt->ar_sha);
memcpy(cp, &(arp_pkt->ar_sip), sizeof(arp_pkt->ar_sip));
cp += sizeof(arp_pkt->ar_sip);
memcpy(cp, &(arp_pkt->ar_tha), sizeof(arp_pkt->ar_tha));
cp += sizeof(arp_pkt->ar_tha);
memcpy(cp, &(arp_pkt->ar_tip), sizeof(arp_pkt->ar_tip));
cp += sizeof(arp_pkt->ar_tip);
/*
* Add padding if specified
*/
if (frame_padding != NULL) {
size_t safe_padding_len;
safe_padding_len = frame_padding_len;
if (packet_size + frame_padding_len > MAX_FRAME) {
safe_padding_len = MAX_FRAME - packet_size;
}
memcpy(cp, frame_padding, safe_padding_len);
cp += safe_padding_len;
packet_size += safe_padding_len;
}
*buf_siz = packet_size;
}
/*
* unmarshal_arp_pkt -- Un Marshal ARP packet from buffer to struct
*
* Inputs:
*
* buffer Pointer to the input buffer
* buf_len Length of input buffer
* frame_hdr The ethernet frame header
* arp_pkt The arp packet data
* extra_data Any extra data after the ARP data (typically padding)
* extra_data_len Length of extra data
* vlan_id 802.1Q VLAN identifier
*
* Returns:
*
* An integer representing the data link framing:
* 0 = Ethernet-II
* 1 = 802.3 with LLC/SNAP
*
* extra_data and extra_data_len are only calculated and returned if
* extra_data is not NULL.
*
* vlan_id is set to -1 if the packet does not use 802.1Q tagging.
*/
int
unmarshal_arp_pkt(const unsigned char *buffer, size_t buf_len,
ether_hdr *frame_hdr, arp_ether_ipv4 *arp_pkt,
unsigned char *extra_data, size_t *extra_data_len,
int *vlan_id) {
const unsigned char *cp;
int framing = FRAMING_ETHERNET_II;
cp = buffer;
/*
* Extract the Ethernet frame header data
*/
memcpy(&(frame_hdr->dest_addr), cp, sizeof(frame_hdr->dest_addr));
cp += sizeof(frame_hdr->dest_addr);
memcpy(&(frame_hdr->src_addr), cp, sizeof(frame_hdr->src_addr));
cp += sizeof(frame_hdr->src_addr);
/*
* Check for 802.1Q VLAN tagging, indicated by a type code of
* 0x8100 (TPID).
*/
if (*cp == 0x81 && *(cp+1) == 0x00) {
uint16_t tci;
cp += 2; /* Skip TPID */
memcpy(&tci, cp, sizeof(tci));
cp += 2; /* Skip TCI */
*vlan_id = ntohs(tci);
*vlan_id &= 0x0fff; /* Mask off PRI and CFI */
} else {
*vlan_id = -1;
}
memcpy(&(frame_hdr->frame_type), cp, sizeof(frame_hdr->frame_type));
cp += sizeof(frame_hdr->frame_type);
/*
* Check for an LLC header with SNAP. If this is present, the 802.2 LLC
* header will contain DSAP=0xAA, SSAP=0xAA, Control=0x03.
* If this 802.2 LLC header is present, skip it and the SNAP header
*/
if (*cp == 0xAA && *(cp+1) == 0xAA && *(cp+2) == 0x03) {
cp += 8; /* Skip eight bytes */
framing = FRAMING_LLC_SNAP;
}
/*
* Extract the ARP packet data
*/
memcpy(&(arp_pkt->ar_hrd), cp, sizeof(arp_pkt->ar_hrd));
cp += sizeof(arp_pkt->ar_hrd);
memcpy(&(arp_pkt->ar_pro), cp, sizeof(arp_pkt->ar_pro));
cp += sizeof(arp_pkt->ar_pro);
memcpy(&(arp_pkt->ar_hln), cp, sizeof(arp_pkt->ar_hln));
cp += sizeof(arp_pkt->ar_hln);
memcpy(&(arp_pkt->ar_pln), cp, sizeof(arp_pkt->ar_pln));
cp += sizeof(arp_pkt->ar_pln);
memcpy(&(arp_pkt->ar_op), cp, sizeof(arp_pkt->ar_op));
cp += sizeof(arp_pkt->ar_op);
memcpy(&(arp_pkt->ar_sha), cp, sizeof(arp_pkt->ar_sha));
cp += sizeof(arp_pkt->ar_sha);
memcpy(&(arp_pkt->ar_sip), cp, sizeof(arp_pkt->ar_sip));
cp += sizeof(arp_pkt->ar_sip);
memcpy(&(arp_pkt->ar_tha), cp, sizeof(arp_pkt->ar_tha));
cp += sizeof(arp_pkt->ar_tha);
memcpy(&(arp_pkt->ar_tip), cp, sizeof(arp_pkt->ar_tip));
cp += sizeof(arp_pkt->ar_tip);
if (extra_data != NULL) {
int length;
/*
* buf_len will not exceed MAX_FRAME
*/
length = buf_len - (cp - buffer);
if (length > 0) { /* Extra data after ARP packet */
memcpy(extra_data, cp, length);
}
*extra_data_len = length;
}
return framing;
}
/*
* add_mac_vendor -- Add MAC/Vendor mappings to the hash table
*
* Inputs:
*
* map_filename The name of the file containing the mappings
*
* Returns:
*
* The number of entries added to the hash table.
*/
int
add_mac_vendor(const char *map_filename) {
static int first_call = 1;
FILE *fp; /* MAC/Vendor file handle */
static const char *oui_pat_str = "([^\t]+)\t[\t ]*([^\t\r\n]+)";
static regex_t oui_pat;
regmatch_t pmatch[3];
size_t key_len;
size_t data_len;
char *key;
char *data;
char *linep;
char *keyp;
char line[MAXLINE];
int line_count;
int result;
ENTRY hash_entry;
/*
* Compile the regex pattern if this is the first time we
* have been called.
*/
if (first_call) {
first_call = 0;
if ((result = regcomp(&oui_pat, oui_pat_str, REG_EXTENDED))) {
char *errbuf;
size_t size;
size = regerror(result, &oui_pat, NULL, 0);
errbuf = Malloc(size);
regerror(result, &oui_pat, errbuf, size);
err_msg("ERROR: cannot compile regex pattern \"%s\": %s",
oui_pat_str, errbuf);
}
}
/*
* Open the file.
*/
if ((fp = fopen(map_filename, "r")) == NULL) {
warn_sys("WARNING: Cannot open MAC/Vendor file %s", map_filename);
return 0;
}
line_count = 0;
while (fgets(line, MAXLINE, fp)) {
if (line[0] == '#' || line[0] == '\n' || line[0] == '\r')
continue; /* Skip blank lines and comments */
result = regexec(&oui_pat, line, 3, pmatch, 0);
if (result == REG_NOMATCH || pmatch[1].rm_so < 0 || pmatch[2].rm_so < 0) {
warn_msg("WARNING: Could not parse oui: %s", line);
} else if (result != 0) {
char *errbuf;
size_t size;
size = regerror(result, &oui_pat, NULL, 0);
errbuf = Malloc(size);
regerror(result, &oui_pat, errbuf, size);
err_msg("ERROR: oui regexec failed: %s", errbuf);
} else {
key_len = pmatch[1].rm_eo - pmatch[1].rm_so;
data_len = pmatch[2].rm_eo - pmatch[2].rm_so;
key = Malloc(key_len+1);
data = Malloc(data_len+1);
/*
* Copy MAC address from line into key, ommitting non-hex characters
* and folding lowercase alphabetic characters to uppercase.
*/
linep = line+pmatch[1].rm_so;
keyp = key;
while (linep != line+pmatch[1].rm_eo) {
if (isxdigit(*linep))
*keyp++ = toupper(*linep);
linep++;
}
*keyp = '\0';
/*
* We cannot use strlcpy because the source is not guaranteed to be
* null terminated. So we use strncpy, specifying one less than the
* total length, and manually null terminate the destination.
*/
strncpy(data, line+pmatch[2].rm_so, data_len);
data[data_len] = '\0';
hash_entry.key = key;
hash_entry.data = data;
if ((hsearch(hash_entry, ENTER)) == NULL) {
err_sys("ERROR: hsearch([%s, %s], ENTER) failed", key, data);
} else {
line_count++;
}
}
}
fclose(fp);
return line_count;
}
/*
* get_mac_vendor_filename -- Determine MAC/Vendor mapping filename
*
* Inputs:
*
* specified_filename The filename specified on the command line
* default_datadir The default data directory
* default_filename The default filename
*
* Returns:
*
* The MAC/Vendor mapping filename.
*
* If a filename was specified as an option on the command line, then
* that filename is used. Otherwise we look for the default filename
* in the current directory, and use that if it's present. Otherwise
* we use the default filename in the default directory.
*
*/
char *
get_mac_vendor_filename(const char *specified_filename,
const char *default_datadir,
const char *default_filename) {
struct stat statbuf;
int status;
char *file_name;
if (!specified_filename) { /* No filename specified */
file_name = make_message("%s", default_filename);
status = stat(file_name, &statbuf);
if (status == -1 && (errno == ENOENT || errno == EACCES)) {
free(file_name);
file_name = make_message("%s/%s", default_datadir, default_filename);
}
} else { /* Filename specified */
file_name = make_message("%s", specified_filename);
}
return file_name;
}
/*
* get_source_ip -- Get IP address associated with given interface
*
* Inputs:
*
* interface_name The name of the network interface
* ip_addr (output) The IP Address associated with the device
*
* Returns:
*
* Zero on success, or -1 on failure.
*/
int
get_source_ip(const char *interface_name, struct in_addr *ip_addr) {
char errbuf[PCAP_ERRBUF_SIZE];
pcap_if_t *alldevsp;
pcap_if_t *device;
pcap_addr_t *addr;
struct sockaddr *sa;
struct sockaddr_in *sin = NULL;
if ((pcap_findalldevs(&alldevsp, errbuf)) != 0) {
printf("pcap_findalldevs: %s\n", errbuf);
}
device = alldevsp;
while (device != NULL && (strcmp(device->name, interface_name) != 0)) {
device = device->next;
}
if (device != NULL) { /* We found a device name match */
for (addr=device->addresses; addr != NULL; addr=addr->next) {
sa = addr->addr;
if (sa->sa_family == AF_INET) {
sin = (struct sockaddr_in *)sa;
break;
}
}
if (sin == NULL) {
memset(&(ip_addr->s_addr), '\0', sizeof(ip_addr->s_addr));
pcap_freealldevs(alldevsp);
return -1;
}
memcpy(ip_addr, &(sin->sin_addr), sizeof(*ip_addr));
pcap_freealldevs(alldevsp);
return 0;
} else {
/* If we reach here then we have not found the interface name in the list
* supplied by pcap_findalldevs() so try getifaddrs() instead if available.
* This happens for legacy Linux alias interfaces with names like eth0:0.
* Ref: https://github.com/royhills/arp-scan/issues/3
*/
#ifdef HAVE_GETIFADDRS
struct ifaddrs *ifap, *ifa;
if ((getifaddrs(&ifap)) != 0) {
err_sys("getifaddrs");
}
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET &&
strcmp(ifa->ifa_name, interface_name) == 0) {
sin = (struct sockaddr_in *)ifa->ifa_addr;
memcpy(ip_addr, &(sin->sin_addr), sizeof(*ip_addr));
return 0;
}
}
freeifaddrs(ifap);
#endif
}
/* If we reach here then we haven't found an IP address */
return -1;
}