module Ethernet::RawSocketFactory

Low-level socket creation functionality.

Low-level socket creation functionality.

Low-level socket creation functionality.

Public Class Methods

socket(eth_device, ether_type = nil) click to toggle source

A raw socket sends and receives raw Ethernet frames.

Args:

eth_device:: device name for the Ethernet card, e.g. 'eth0'
ether_type:: only receive Ethernet frames with this protocol number
# File lib/ethernet/raw_socket_factory.rb, line 13
def self.socket(eth_device, ether_type = nil)
  # This method is redefined in platform-specific implementations.
  raise "Unsupported os #{Ethernet::Provisioning::OS}"
end

Private Class Methods

bpf_pseudo_socket() click to toggle source

Returns a BPF file descriptor that acts almost like a link-layer socket.

BPF means Berkeley Packet Filter, and works on FreeBSD-like kernels, including Darwin.

# File lib/ethernet/raw_socket_factory_darwin.rb, line 24
def bpf_pseudo_socket
  3.times do
    Dir['/dev/bpf*'].sort.each do |name|
      begin
        s = File.open name, 'r+b'
        s.sync = true
        return s
      rescue Errno::EBUSY
        # Move to the next BPF device.
      end
    end
  end
  return nil
end
htonl(long_integer) click to toggle source

Converts a 32-bit integer from host-order to network-order.

# File lib/ethernet/raw_socket_factory.rb, line 26
def htonl(long_integer)
  [long_integer].pack('N').unpack('L').first
end
htons(short_integer) click to toggle source

Converts a 16-bit integer from host-order to network-order.

# File lib/ethernet/raw_socket_factory.rb, line 20
def htons(short_integer)
  [short_integer].pack('n').unpack('S').first
end
set_bpf_eth_device(bpf, eth_device, ether_type) click to toggle source

Binds a BPF file descriptor to a device and limits packet capture.

BPF means Berkeley Packet Filter, and works on FreeBSD-like kernels, including Darwin.

This method also sets flags so that the socket behaves as much as possible like a Linux PF_PACKET raw socket.

# File lib/ethernet/raw_socket_factory_darwin.rb, line 47
def set_bpf_eth_device(bpf, eth_device, ether_type)
  # BIOCSETIF in /usr/include/net/bpf.h
  # _IOW in /usr/include/sys/ioccom.h
  # struct ifreq in /usr/include/net/if.h
  bpf.ioctl 0x8020426C, [eth_device].pack('a32')
  
  # Receive packets as soon as they're available.
  # BIOCIMMEDIATE in /usr/include/net/bpf.h
  # _IOW in /usr/include/sys/ioccom.h
  bpf.ioctl 0x80044270, [1].pack('L')
  
  # Don't automatically set the Ethernet header.
  # BIOCSHDRCMPLT in /usr/include/net/bpf.h
  # _IOW in /usr/include/sys/ioccom.h
  bpf.ioctl 0x80044275, [1].pack('L')
  
  # Don't receive the packets that we sent ourselves.
  # BIOCSSEESENT in /usr/include/net/bpf.h
  # _IOW in /usr/include/sys/ioccom.h
  bpf.ioctl 0x80044275, [0].pack('L')
  
  # BPF filter programming constants in /usr/include/net/bpf.h
  if ether_type
    filter = [
      # A <- packet Ethernet type
      [0x28, 0, 0, 12],  # BPF_LD + BPF_H + BPF_ABS
      # if A == ether_type jump above next instruction
      [0x15, 1, 0, ether_type],  # BPF_JMP + BPF_JEQ + BPF_K
      # drop packet (ret K = 0)
      [0x06, 0, 0, 0]  # BPF_RET + BPF_K
    ]
  else
    filter = []
  end
  
  ether_mac = Ethernet::Devices.mac eth_device
  filter += [
    # A <- first byte of destination MAC address
    [0x30, 0, 0, 0],  # BPF_LD + BPF_B + BPF_ABS
    # if A & 1 (multicast MAC address) jump above exact MAC match
    [0x45, 5, 0, 1],   # BPF_JMP + BPF_JSET + BPF_K
    
    # A <- first 4 bytes of destination MAC addres
    [0x20, 0, 0, 0],  # BPF_LD + BPF_W + BPF_ABS
    # if A != first 4 bytes of local MAC address jump to drop instruction
    [0x15, 0, 2, ether_mac.unpack('N').first],  # BPF_JMP + BPF_JEQ + BPF_K
    # A <- last 2 bytes of destination MAC address
    [0x28, 0, 0, 4],  # BPF_LD + BPF_H + BPF_ABS
    # if A == last 2 bytes of local MAC address jump above next instruction
    [0x15, 1, 0,
        ether_mac.unpack('@4n').first],  # BPF_JMP + BPF_JEQ + BPF_K
    # drop packet (ret K = 0)
    [0x06, 0, 0, 0],  # BPF_RET + BPF_K

    # A <- packet length
    [0x80, 0, 0, 0],  # BPF_LD + BPF_W + BPF_LEN
    # ret A (accept the entire packet)
    [0x16, 0, 0, 0]  # BPF_RET + BPF_A
  ]
  filter_code = filter.map { |i| i.pack('SCCL') }.join('')
  # struct bpf_program in /usr/include/net/bpf.h
  filter_code_ptr = FFI::MemoryPointer.new :char, filter_code.length + 1
  filter_code_ptr.write_string filter_code
  if Ethernet::Provisioning::POINTER_SIZE == 8
    pack_spec = 'QQ'
  else
    pack_spec = 'LL'
  end
  bpf_program = [filter.length, filter_code_ptr.address].pack pack_spec
  # BIOCSETF in /usr/include/net/bpf.h
  # _IOW in /usr/include/sys/iocom.h
  bpf.ioctl 0x80104267, bpf_program
end