CrackedRuby CrackedRuby

Overview

The Address Resolution Protocol (ARP) operates at the network interface layer to resolve Internet Protocol (IP) addresses into Media Access Control (MAC) addresses within local networks. When a device needs to communicate with another device on the same network segment, it knows the destination IP address but requires the corresponding MAC address to frame the data link layer packet. ARP provides the mechanism for this translation.

ARP functions through a request-response pattern. A device broadcasts an ARP request containing the target IP address to all devices on the local network segment. The device with the matching IP address responds with an ARP reply containing its MAC address. Both the requesting device and the responding device cache this address mapping to avoid repeated broadcasts for subsequent communications.

The protocol specification, defined in RFC 826, describes a hardware-independent method for address resolution that works across different network technologies. While most commonly associated with IPv4 and Ethernet networks, ARP supports multiple protocol and hardware address formats through its flexible packet structure.

# Example ARP cache inspection in Ruby using system commands
require 'open3'

def read_arp_cache
  stdout, stderr, status = Open3.capture3('arp', '-a')
  
  if status.success?
    entries = []
    stdout.each_line do |line|
      if line =~ /\((\d+\.\d+\.\d+\.\d+)\) at ([0-9a-f:]+)/
        entries << { ip: $1, mac: $2 }
      end
    end
    entries
  else
    raise "Failed to read ARP cache: #{stderr}"
  end
end

cache = read_arp_cache
cache.each { |entry| puts "#{entry[:ip]} -> #{entry[:mac]}" }
# => 192.168.1.1 -> aa:bb:cc:dd:ee:ff
# => 192.168.1.50 -> 11:22:33:44:55:66

ARP cache entries have limited lifetimes to accommodate network topology changes. Operating systems implement varying cache timeout policies, typically ranging from several minutes to hours for valid entries. This temporal aspect introduces complexity in network programming and diagnostics.

Key Principles

ARP operates on the principle that devices within the same broadcast domain can communicate directly at the data link layer once they know each other's hardware addresses. The protocol assumes a broadcast-capable medium where all devices can receive packets addressed to a broadcast address.

The ARP packet structure contains fields for hardware type, protocol type, hardware address length, protocol address length, operation code, and the sender and target hardware and protocol addresses. The hardware type field typically contains value 1 for Ethernet, while the protocol type field contains 0x0800 for IPv4. Operation codes distinguish between ARP requests (1) and ARP replies (2).

When a device needs to send a packet to an IP address on the local network, it first checks its ARP cache for an existing mapping. If no entry exists or the entry has expired, the device constructs an ARP request packet with the sender's IP and MAC addresses in the sender fields, the target IP address in the target protocol address field, and zeroes or a broadcast address in the target hardware address field. The device broadcasts this request to the MAC address ff:ff:ff:ff:ff:ff.

All devices on the network segment receive the broadcast ARP request. Each device compares the target IP address in the request with its own IP address. Devices with non-matching IP addresses discard the packet. The device with the matching IP address constructs an ARP reply containing its MAC address and sends it directly to the requester using the sender hardware and protocol addresses from the request.

Both the requesting and responding devices update their ARP caches with the learned mappings. The requester learns the target's MAC address from the reply, while the responder learns the requester's mapping from the request itself. This optimization reduces future ARP traffic by caching mappings proactively.

# Conceptual ARP packet structure in Ruby
class ARPPacket
  HARDWARE_TYPE_ETHERNET = 1
  PROTOCOL_TYPE_IPV4 = 0x0800
  OPERATION_REQUEST = 1
  OPERATION_REPLY = 2
  
  attr_accessor :hardware_type, :protocol_type, :hw_addr_length, 
                :proto_addr_length, :operation, :sender_hw_addr,
                :sender_proto_addr, :target_hw_addr, :target_proto_addr
  
  def initialize
    @hardware_type = HARDWARE_TYPE_ETHERNET
    @protocol_type = PROTOCOL_TYPE_IPV4
    @hw_addr_length = 6  # MAC address bytes
    @proto_addr_length = 4  # IPv4 address bytes
  end
  
  def request?
    operation == OPERATION_REQUEST
  end
  
  def reply?
    operation == OPERATION_REPLY
  end
  
  def to_bytes
    # Simplified serialization
    [
      hardware_type,
      protocol_type,
      hw_addr_length,
      proto_addr_length,
      operation
    ].pack('nnnCC') + 
    sender_hw_addr + sender_proto_addr +
    target_hw_addr + target_proto_addr
  end
end

ARP cache management follows specific rules for entry lifecycle. Dynamic entries result from ARP request-reply exchanges and expire after timeout periods. Static entries can be manually configured and persist until explicitly removed or system restart. Incomplete entries occur when an ARP request has been sent but no reply received, typically maintained for short durations before retry or discard.

The protocol includes no authentication or verification mechanism in its basic form. Any device can send ARP replies, and receiving devices accept these replies without validation. This design choice prioritizes simplicity and performance over security, reflecting the protocol's origins in trusted network environments.

Ruby Implementation

Ruby provides multiple approaches for working with ARP, ranging from system command execution to raw socket programming. The standard library includes socket support that enables low-level network operations, though ARP-specific functionality typically requires privileged access and careful packet construction.

The most direct approach uses system commands through backticks, system calls, or the Open3 module. This method relies on operating system utilities like arp, ip, or arping to interact with the ARP cache and send ARP requests. While not pure Ruby, this approach offers simplicity and cross-platform compatibility.

require 'open3'

class ARPCacheManager
  def self.get_entry(ip_address)
    stdout, stderr, status = Open3.capture3('arp', '-n', ip_address)
    
    return nil unless status.success?
    
    if stdout =~ /(\h{2}:\h{2}:\h{2}:\h{2}:\h{2}:\h{2})/
      { ip: ip_address, mac: $1, type: :dynamic }
    end
  end
  
  def self.add_static_entry(ip_address, mac_address)
    # Requires root/administrator privileges
    stdout, stderr, status = Open3.capture3(
      'arp', '-s', ip_address, mac_address
    )
    
    status.success?
  end
  
  def self.delete_entry(ip_address)
    stdout, stderr, status = Open3.capture3('arp', '-d', ip_address)
    status.success?
  end
  
  def self.all_entries
    stdout, stderr, status = Open3.capture3('arp', '-a')
    return [] unless status.success?
    
    entries = []
    stdout.each_line do |line|
      next unless line =~ /\((\d+\.\d+\.\d+\.\d+)\) at ([0-9a-f:]+)/
      
      entries << {
        ip: $1,
        mac: $2,
        type: line.include?('PERM') ? :static : :dynamic
      }
    end
    
    entries
  end
end

# Usage
entry = ARPCacheManager.get_entry('192.168.1.1')
puts "Router MAC: #{entry[:mac]}" if entry
# => Router MAC: aa:bb:cc:dd:ee:ff

For more direct control, Ruby's socket library enables raw packet construction and transmission. Raw sockets require elevated privileges and knowledge of packet structure. The PacketFu gem provides higher-level abstractions for packet manipulation.

require 'socket'

class RawARPSender
  def initialize(interface)
    @interface = interface
    @socket = Socket.new(Socket::AF_PACKET, Socket::SOCK_RAW, 0x0806)
    
    # Get interface index
    ifreq = [@interface].pack('a16')
    @socket.ioctl(0x8933, ifreq)  # SIOCGIFINDEX
    @if_index = ifreq.unpack('a16i')[1]
  end
  
  def send_arp_request(target_ip, source_ip, source_mac)
    packet = build_arp_request(target_ip, source_ip, source_mac)
    
    # Socket address structure for AF_PACKET
    sll = [
      Socket::AF_PACKET,
      0x0806,  # ETH_P_ARP in network byte order
      @if_index,
      1,  # PACKET_BROADCAST
      6,  # Hardware address length
      'ff:ff:ff:ff:ff:ff'.split(':').map(&:hex).pack('C6')
    ].pack('SSnCCa8')
    
    @socket.send(packet, 0, sll)
  end
  
  private
  
  def build_arp_request(target_ip, source_ip, source_mac)
    # Ethernet header
    eth_dst = ['ff', 'ff', 'ff', 'ff', 'ff', 'ff'].map(&:hex).pack('C6')
    eth_src = source_mac.split(':').map(&:hex).pack('C6')
    eth_type = [0x0806].pack('n')
    
    # ARP header
    hw_type = [1].pack('n')  # Ethernet
    proto_type = [0x0800].pack('n')  # IPv4
    hw_size = [6].pack('C')
    proto_size = [4].pack('C')
    opcode = [1].pack('n')  # Request
    
    # ARP payload
    sender_mac = source_mac.split(':').map(&:hex).pack('C6')
    sender_ip = source_ip.split('.').map(&:to_i).pack('C4')
    target_mac = [0, 0, 0, 0, 0, 0].pack('C6')
    target_ip_bytes = target_ip.split('.').map(&:to_i).pack('C4')
    
    eth_dst + eth_src + eth_type +
      hw_type + proto_type + hw_size + proto_size + opcode +
      sender_mac + sender_ip + target_mac + target_ip_bytes
  end
end

The pcap library, accessible through the pcaprub gem, provides packet capture capabilities for monitoring ARP traffic. This enables analysis of ARP requests and replies on the network.

require 'pcaprub'

class ARPMonitor
  def initialize(interface)
    @interface = interface
    @capture = PCAPRUB::Pcap.open_live(interface, 65535, true, 1000)
    @capture.setfilter('arp')
  end
  
  def monitor(duration = 60)
    packets = []
    start_time = Time.now
    
    while Time.now - start_time < duration
      @capture.each do |packet_data|
        packets << parse_arp_packet(packet_data)
      end
    end
    
    packets
  end
  
  private
  
  def parse_arp_packet(data)
    # Skip Ethernet header (14 bytes)
    arp_data = data[14..-1]
    
    hw_type = arp_data[0..1].unpack1('n')
    proto_type = arp_data[2..3].unpack1('n')
    hw_size = arp_data[4].unpack1('C')
    proto_size = arp_data[5].unpack1('C')
    opcode = arp_data[6..7].unpack1('n')
    
    sender_mac = arp_data[8..13].unpack('C6').map { |b| '%02x' % b }.join(':')
    sender_ip = arp_data[14..17].unpack('C4').join('.')
    target_mac = arp_data[18..23].unpack('C6').map { |b| '%02x' % b }.join(':')
    target_ip = arp_data[24..27].unpack('C4').join('.')
    
    {
      type: opcode == 1 ? :request : :reply,
      sender_mac: sender_mac,
      sender_ip: sender_ip,
      target_mac: target_mac,
      target_ip: target_ip,
      timestamp: Time.now
    }
  end
end

Practical Examples

Network diagnostic tools frequently query ARP caches to troubleshoot connectivity issues. A device unable to communicate with another device on the local network may lack an ARP entry or possess an incorrect mapping. Verifying ARP cache contents helps identify such problems.

require 'open3'
require 'ipaddr'

class NetworkDiagnostics
  def self.diagnose_connectivity(target_ip)
    results = {
      ip: target_ip,
      ping_success: false,
      arp_entry: nil,
      recommendations: []
    }
    
    # Check ARP cache
    arp_output, = Open3.capture3('arp', '-n', target_ip)
    if arp_output =~ /([0-9a-f:]{17})/
      results[:arp_entry] = $1
    else
      results[:recommendations] << "No ARP entry found - host may be offline or unreachable"
    end
    
    # Test connectivity
    ping_output, = Open3.capture3('ping', '-c', '1', '-W', '1', target_ip)
    results[:ping_success] = ping_output.include?('1 received')
    
    # Analyze results
    if results[:ping_success] && results[:arp_entry]
      results[:recommendations] << "Connectivity confirmed"
    elsif !results[:ping_success] && results[:arp_entry]
      results[:recommendations] << "ARP entry exists but ping fails - check firewall or routing"
    elsif results[:ping_success] && !results[:arp_entry]
      results[:recommendations] << "Ping succeeded - ARP entry may have expired, run diagnosis again"
    else
      results[:recommendations] << "Cannot reach host - verify IP address and network connectivity"
    end
    
    results
  end
end

# Usage
diagnosis = NetworkDiagnostics.diagnose_connectivity('192.168.1.100')
puts "Target: #{diagnosis[:ip]}"
puts "ARP Entry: #{diagnosis[:arp_entry] || 'None'}"
puts "Ping: #{diagnosis[:ping_success] ? 'Success' : 'Failed'}"
diagnosis[:recommendations].each { |r| puts "- #{r}" }

Network mapping applications scan local networks to discover active hosts. This process sends ARP requests to all possible IP addresses in a subnet and records which addresses respond. The resulting map shows the network topology and active devices.

require 'open3'
require 'ipaddr'
require 'thread'

class NetworkScanner
  def initialize(network_cidr)
    @network = IPAddr.new(network_cidr)
    @mutex = Mutex.new
    @results = []
  end
  
  def scan(threads: 50)
    queue = Queue.new
    
    # Generate all host IPs in network
    @network.to_range.each do |ip|
      next if ip == @network.to_range.first  # Skip network address
      next if ip == @network.to_range.last   # Skip broadcast address
      queue << ip.to_s
    end
    
    # Create worker threads
    workers = threads.times.map do
      Thread.new do
        while !queue.empty?
          begin
            ip = queue.pop(true)
            check_host(ip)
          rescue ThreadError
            break  # Queue empty
          end
        end
      end
    end
    
    workers.each(&:join)
    @results
  end
  
  private
  
  def check_host(ip)
    # Send single ping to trigger ARP request
    stdout, = Open3.capture3('ping', '-c', '1', '-W', '1', ip)
    
    if stdout.include?('1 received')
      # Get ARP entry
      arp_output, = Open3.capture3('arp', '-n', ip)
      
      if arp_output =~ /([0-9a-f:]{17})/
        @mutex.synchronize do
          @results << {
            ip: ip,
            mac: $1,
            timestamp: Time.now
          }
        end
      end
    end
  end
end

# Usage
scanner = NetworkScanner.new('192.168.1.0/24')
hosts = scanner.scan(threads: 100)

puts "Found #{hosts.size} active hosts:"
hosts.each do |host|
  puts "#{host[:ip].ljust(15)} #{host[:mac]}"
end
# => 192.168.1.1    aa:bb:cc:dd:ee:ff
# => 192.168.1.50   11:22:33:44:55:66
# => 192.168.1.100  22:33:44:55:66:77

DHCP server implementations maintain mappings between MAC addresses and assigned IP addresses. When a DHCP server receives a request from a client, it uses the client's MAC address (from the DHCP packet and potentially from ARP) to determine which IP address to assign or whether to renew an existing lease.

class DHCPARPTracker
  def initialize
    @leases = {}  # MAC => { ip:, expires:, hostname: }
    @arp_cache = {}  # IP => { mac:, last_seen: }
  end
  
  def record_lease(mac_address, ip_address, lease_duration, hostname = nil)
    @leases[mac_address] = {
      ip: ip_address,
      expires: Time.now + lease_duration,
      hostname: hostname
    }
    
    update_arp_cache(ip_address, mac_address)
  end
  
  def update_arp_cache(ip_address, mac_address)
    @arp_cache[ip_address] = {
      mac: mac_address,
      last_seen: Time.now
    }
  end
  
  def verify_lease(mac_address)
    lease = @leases[mac_address]
    return nil unless lease
    
    # Check if lease expired
    return nil if lease[:expires] < Time.now
    
    # Verify ARP cache consistency
    arp_entry = @arp_cache[lease[:ip]]
    if arp_entry && arp_entry[:mac] != mac_address
      # MAC address conflict detected
      return { conflict: true, conflicting_mac: arp_entry[:mac] }
    end
    
    lease
  end
  
  def detect_rogue_devices
    rogue = []
    
    # Get current ARP cache from system
    stdout, = Open3.capture3('arp', '-a')
    stdout.each_line do |line|
      next unless line =~ /\((\d+\.\d+\.\d+\.\d+)\) at ([0-9a-f:]+)/
      
      ip = $1
      mac = $2
      
      # Check if IP is in known leases
      lease = @leases.values.find { |l| l[:ip] == ip }
      
      if lease && lease[:mac] != mac
        rogue << {
          ip: ip,
          expected_mac: lease[:mac],
          actual_mac: mac,
          hostname: lease[:hostname]
        }
      end
    end
    
    rogue
  end
end

Security Implications

ARP lacks authentication mechanisms, making it vulnerable to spoofing attacks where malicious actors send forged ARP replies to poison cache entries on victim machines. An attacker can claim ownership of any IP address by broadcasting ARP replies associating that IP with the attacker's MAC address. Victim machines accept these replies and update their ARP caches accordingly.

ARP spoofing enables man-in-the-middle attacks within local networks. An attacker positions themselves between two communicating parties by poisoning both machines' ARP caches. Traffic destined for each legitimate party flows through the attacker's machine, allowing interception, modification, or selective forwarding of packets.

# Example: Detecting potential ARP spoofing
class ARPSpoofDetector
  def initialize(interface)
    @known_mappings = {}  # IP => [MAC addresses seen]
    @alerts = []
  end
  
  def process_arp_packet(packet)
    sender_ip = packet[:sender_ip]
    sender_mac = packet[:sender_mac]
    
    if @known_mappings.key?(sender_ip)
      if !@known_mappings[sender_ip].include?(sender_mac)
        # New MAC for existing IP - potential spoofing
        @alerts << {
          type: :ip_mac_change,
          ip: sender_ip,
          old_macs: @known_mappings[sender_ip].dup,
          new_mac: sender_mac,
          timestamp: Time.now,
          severity: :high
        }
        
        @known_mappings[sender_ip] << sender_mac
      end
    else
      @known_mappings[sender_ip] = [sender_mac]
    end
    
    # Check for gratuitous ARP (sender IP == target IP)
    if packet[:sender_ip] == packet[:target_ip]
      @alerts << {
        type: :gratuitous_arp,
        ip: packet[:sender_ip],
        mac: packet[:sender_mac],
        timestamp: Time.now,
        severity: :medium
      }
    end
    
    # Detect rapid ARP replies from different MACs
    detect_reply_flood(sender_ip)
  end
  
  def alerts
    @alerts
  end
  
  private
  
  def detect_reply_flood(ip)
    recent_packets = @known_mappings[ip]
    return unless recent_packets && recent_packets.size > 5
    
    # Check if multiple MACs seen in short timeframe
    # (simplified - real implementation would track timestamps)
    unique_macs = recent_packets.uniq
    if unique_macs.size > 2
      @alerts << {
        type: :multiple_mac_flood,
        ip: ip,
        macs: unique_macs,
        timestamp: Time.now,
        severity: :critical
      }
    end
  end
end

Static ARP entries provide defense against spoofing by preventing cache updates for critical hosts. Administrators manually configure these entries on sensitive systems to establish fixed IP-to-MAC mappings. Static entries persist until explicit removal, resisting ARP reply attacks. However, static entries create management overhead and fail to accommodate legitimate hardware replacements.

Dynamic ARP inspection (DAI), implemented on network switches, validates ARP packets against a trusted database of IP-to-MAC bindings. Switches configured with DAI intercept ARP packets and compare them to DHCP snooping tables or manually configured bindings. Invalid packets are dropped before reaching other network hosts. DAI operates transparently to end systems while providing protection at the infrastructure level.

# Simplified DAI table management
class DynamicARPInspection
  def initialize
    @trusted_bindings = {}  # IP => MAC
    @trusted_ports = []
  end
  
  def add_trusted_binding(ip_address, mac_address)
    @trusted_bindings[ip_address] = mac_address.downcase
  end
  
  def add_trusted_port(port_id)
    @trusted_ports << port_id
  end
  
  def validate_arp_packet(packet, ingress_port)
    # Trust packets from trusted ports
    return :trusted if @trusted_ports.include?(ingress_port)
    
    sender_ip = packet[:sender_ip]
    sender_mac = packet[:sender_mac].downcase
    
    # Check against trusted bindings
    if @trusted_bindings.key?(sender_ip)
      if @trusted_bindings[sender_ip] == sender_mac
        return :valid
      else
        return {
          result: :invalid,
          reason: "MAC mismatch",
          expected: @trusted_bindings[sender_ip],
          received: sender_mac
        }
      end
    end
    
    # No binding exists - depends on policy
    # (strict mode would drop, permissive would allow)
    return :no_binding
  end
end

Gratuitous ARP packets, where a host announces its own IP-to-MAC mapping unsolicited, serve legitimate purposes like detecting IP conflicts and updating cached entries after network changes. However, attackers exploit gratuitous ARP for cache poisoning since receiving hosts update their caches without verification. Monitoring for excessive gratuitous ARP traffic or gratuitous ARP from unexpected sources helps identify attacks.

Rate limiting ARP traffic at network boundaries prevents ARP storms and denial-of-service attacks. Excessive ARP requests can overwhelm switches and hosts, degrading network performance. Implementing rate limits on ARP packet processing protects against both malicious attacks and misconfigured devices that generate excessive legitimate ARP traffic.

Common Pitfalls

ARP cache timeout variations across operating systems cause inconsistent behavior in networked applications. Linux systems typically maintain entries for 60-120 seconds, Windows systems for 2-10 minutes, and other systems implement different policies. Applications assuming specific timeout behavior encounter failures when deployed on different platforms or when cache entries expire unexpectedly.

# Handling cache timeout variations
class ReliableARPResolver
  def initialize
    @resolution_cache = {}
    @cache_duration = 30  # Conservative timeout
  end
  
  def resolve_mac(ip_address)
    cached = @resolution_cache[ip_address]
    
    # Use cached value if fresh
    if cached && (Time.now - cached[:timestamp]) < @cache_duration
      return cached[:mac]
    end
    
    # Force fresh resolution
    mac = perform_resolution(ip_address)
    
    if mac
      @resolution_cache[ip_address] = {
        mac: mac,
        timestamp: Time.now
      }
    end
    
    mac
  end
  
  private
  
  def perform_resolution(ip_address)
    # Clear any stale entry
    Open3.capture3('arp', '-d', ip_address)
    
    # Generate ARP request via ping
    Open3.capture3('ping', '-c', '1', '-W', '2', ip_address)
    
    # Read fresh entry
    stdout, = Open3.capture3('arp', '-n', ip_address)
    
    if stdout =~ /([0-9a-f:]{17})/
      $1
    end
  end
end

Incomplete ARP entries occur when a host sends an ARP request but receives no reply. The operating system maintains an incomplete entry for a brief period, typically rejecting outbound packets to that destination. Applications unaware of incomplete entries continue attempting to send data, experiencing silent failures or timeouts. Checking for incomplete entries before critical operations prevents these issues.

Broadcast storms result from misconfigured devices or loops in network topology generating excessive ARP requests. Each broadcast ARP request propagates to all devices on the network segment, and cascading failures occur when devices respond to or regenerate ARP traffic faster than switches can process. Network monitoring tools detecting abnormal ARP traffic volumes help identify and mitigate storms.

ARP table overflow attacks attempt to exhaust switch CAM table capacity by generating ARP traffic for numerous spoofed MAC addresses. Switches fail open when their tables fill, broadcasting all traffic to all ports and enabling promiscuous packet capture. Implementing MAC address limits per port and monitoring table utilization prevents successful overflow attacks.

# Monitoring for ARP anomalies
class ARPAnomalyDetector
  def initialize
    @request_counts = Hash.new(0)
    @window_start = Time.now
    @window_duration = 60  # 1 minute windows
  end
  
  def process_packet(packet)
    current_time = Time.now
    
    # Reset window if needed
    if current_time - @window_start > @window_duration
      analyze_window
      @request_counts.clear
      @window_start = current_time
    end
    
    if packet[:type] == :request
      source = packet[:sender_mac]
      @request_counts[source] += 1
    end
  end
  
  private
  
  def analyze_window
    return if @request_counts.empty?
    
    # Detect excessive requests from single source
    @request_counts.each do |mac, count|
      if count > 100  # Threshold for anomaly
        puts "Warning: #{mac} sent #{count} ARP requests in #{@window_duration}s"
      end
    end
    
    # Detect potential table overflow attempts
    unique_sources = @request_counts.size
    if unique_sources > 1000
      puts "Warning: #{unique_sources} unique MAC addresses seen - possible table overflow attack"
    end
  end
end

Gratuitous ARP conflicts arise when multiple hosts claim the same IP address, typically due to misconfiguration or malicious activity. Receiving hosts update their caches with the most recent gratuitous ARP, causing connectivity to flip between the conflicting hosts. IP address conflict detection mechanisms, combined with proper DHCP configuration and static IP management, prevent these scenarios.

Mobile device behavior introduces complexity when devices move between networks or reconnect after sleep periods. These devices send gratuitous ARP packets to announce their presence and update cached entries on other hosts. Applications sensitive to IP-to-MAC mapping changes must handle these mobility events gracefully rather than treating them as errors.

Tools & Ecosystem

The arp command-line utility provides basic ARP cache inspection and manipulation across Unix-like systems and Windows. The tool displays cached entries, adds static mappings, and deletes entries. While syntax varies slightly between platforms (arp -a versus arp -an versus ip neighbor), the core functionality remains consistent.

# Cross-platform ARP utility wrapper
class ARPUtil
  def self.platform
    case RbConfig::CONFIG['host_os']
    when /darwin|mac os/
      :macos
    when /linux/
      :linux
    when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
      :windows
    else
      :unknown
    end
  end
  
  def self.list_entries
    case platform
    when :macos, :windows
      stdout, = Open3.capture3('arp', '-a')
    when :linux
      # Try ip neighbor first, fall back to arp
      stdout, stderr, status = Open3.capture3('ip', 'neighbor', 'show')
      if !status.success?
        stdout, = Open3.capture3('arp', '-a')
      end
    end
    
    parse_entries(stdout, platform)
  end
  
  private
  
  def self.parse_entries(output, platform)
    entries = []
    
    output.each_line do |line|
      case platform
      when :linux
        # Format: 192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE
        if line =~ /^(\S+) dev (\S+) lladdr ([0-9a-f:]+) (\S+)/
          entries << {
            ip: $1,
            interface: $2,
            mac: $3,
            state: $4.downcase.to_sym
          }
        end
      when :macos, :windows
        # Format: hostname (192.168.1.1) at aa:bb:cc:dd:ee:ff on en0 [ethernet]
        if line =~ /\((\d+\.\d+\.\d+\.\d+)\) at ([0-9a-f:]+)/
          entries << {
            ip: $1,
            mac: $2
          }
        end
      end
    end
    
    entries
  end
end

Arping generates ARP requests to specific IP addresses and reports responses, similar to ICMP ping but operating at the data link layer. The tool verifies host presence when ICMP is blocked, detects duplicate IP addresses, and measures response times. Arping requires raw socket access and typically needs elevated privileges.

Tcpdump and Wireshark capture and analyze network traffic including ARP packets. These tools reveal ARP request-reply patterns, identify spoofing attempts, and diagnose cache inconsistencies. Applying ARP-specific filters (arp in tcpdump, arp in Wireshark) isolates ARP traffic for focused analysis.

The arp-scan tool performs rapid network discovery by sending ARP requests to all addresses in specified ranges. The tool provides features beyond basic arping including customizable timing, MAC address vendor identification, and output formatting options. Security professionals use arp-scan for network reconnaissance and asset inventory.

# Using arp-scan results in Ruby
class NetworkInventory
  def self.scan_network(network_cidr)
    stdout, stderr, status = Open3.capture3(
      'arp-scan',
      '--interface=eth0',
      '--numeric',
      '--plain',
      network_cidr
    )
    
    unless status.success?
      raise "arp-scan failed: #{stderr}"
    end
    
    devices = []
    stdout.each_line do |line|
      if line =~ /^(\d+\.\d+\.\d+\.\d+)\s+([0-9a-f:]+)\s+(.+)$/
        devices << {
          ip: $1,
          mac: $2,
          vendor: $3.strip
        }
      end
    end
    
    devices
  end
end

The arpwatch daemon monitors networks for ARP activity, recording observed IP-to-MAC pairings and sending notifications when changes occur. The tool maintains a database of mappings and alerts administrators to new hosts, address changes, and flip-flops. Arpwatch helps detect spoofing attacks and track network changes over time.

Ruby gems providing network functionality include PacketFu for packet creation and parsing, Pcaprub for packet capture, and Net::Ping for network reachability testing. These libraries enable building custom ARP tools and integrating ARP functionality into larger applications.

Switch management interfaces expose ARP table contents and Dynamic ARP Inspection configuration. SNMP queries retrieve MAC address tables and port status. RESTful APIs on modern switches provide programmatic access to network state including learned ARP bindings.

Reference

ARP Packet Structure

Field Length Description
Hardware Type 2 bytes Network link protocol type (1 for Ethernet)
Protocol Type 2 bytes Protocol being resolved (0x0800 for IPv4)
Hardware Address Length 1 byte MAC address length in bytes (6 for Ethernet)
Protocol Address Length 1 byte Protocol address length (4 for IPv4)
Operation 2 bytes Request (1) or Reply (2)
Sender Hardware Address 6 bytes MAC address of sender
Sender Protocol Address 4 bytes IP address of sender
Target Hardware Address 6 bytes MAC address of target
Target Protocol Address 4 bytes IP address of target

ARP Cache Entry States

State Description Typical Duration
REACHABLE Valid entry recently confirmed 60-300 seconds
STALE Entry not recently confirmed but usable Until next use
DELAY First packet sent, waiting for confirmation 5 seconds
PROBE Actively probing for reachability 1 second per probe
INCOMPLETE ARP request sent, no reply received 3-5 seconds
FAILED No response after multiple probes Immediate removal
PERMANENT Static entry Until manual removal

Common ARP Command Operations

Operation Linux macOS Windows
Display cache arp -a or ip neighbor arp -a arp -a
Display numeric arp -n or ip -n neighbor arp -an arp -a
Delete entry arp -d IP or ip neighbor del IP arp -d IP arp -d IP
Add static entry arp -s IP MAC or ip neighbor add IP lladdr MAC dev IFACE arp -s IP MAC arp -s IP MAC
Flush all entries ip neighbor flush all arp -ad (requires root) netsh interface ip delete arpcache

Ruby ARP Libraries and Gems

Library Purpose Installation
PacketFu Packet manipulation and creation gem install packetfu
Pcaprub Packet capture bindings gem install pcaprub
Net::Ping Network reachability testing gem install net-ping
Open3 Execute system commands safely Built-in standard library
Socket Low-level socket operations Built-in standard library

Security Monitoring Thresholds

Metric Normal Range Alert Threshold Critical Threshold
ARP requests per host 10-50/min 100/min 500/min
Unique MAC addresses Actual device count 2x device count 10x device count
IP-MAC changes per hour 0-5 10 50
Gratuitous ARP per host 0-2/hour 10/hour 50/hour
Broadcast ARP ratio 80-95% 98% 99.5%

ARP Spoofing Detection Indicators

Indicator Description Response Priority
MAC address change Existing IP associates with different MAC High
Duplicate IP Multiple MACs claim same IP address Critical
Gratuitous ARP flood Excessive unsolicited ARP announcements High
ARP reply without request Reply received when no request sent Medium
Reply from unexpected source Response from MAC other than target High
Conflicting gateway MAC Default gateway MAC differs across hosts Critical

Platform-Specific ARP Cache Timeouts

Platform Dynamic Entry Incomplete Entry Static Entry
Linux 60-300s (configurable) 1-3s Permanent
macOS 1200s 3s Permanent
Windows 10/11 120-600s 3s Permanent
FreeBSD 1200s 2s Permanent
Cisco IOS 14400s N/A Permanent