Libnet4r

Libnet4r is a Ruby extension that provides bindings to the excellent Libnet packet construction and injection library. It is however more than just a straight port of the Libnet API to Ruby methods. In addition to the packet builder and injection functions that Libnet provides, you also get object classes for each header type that contain decode methods for decoding packed byte strings. Also, the header builder methods have been written in a much more Ruby like manner using objects and blocks as apposed to the long parameter lists that the Libnet C functions use.

Installing Libnet4r

Get Libnet4r from RubyForge.

$ gem install libnet4r

Example

require 'libnet4r'

# create a new Libnet object for injecting at the link layer using device en0
l = Libnet.new(:link, 'en0')

# create a random 200 byte string to use as our UDP datagram payload
srand Time.now.to_i
bytes   = Array.new(200) { |i| rand(255) }
payload = bytes.pack("C*")

# build the UDP header
udp = l.build_udp do |udp|
  udp.src_port = 0xdead
  udp.dst_port = 0xbeef
  udp.length   = Libnet::HL_UDP + payload.length
  udp.checksum = 0  # set to 0 to instruct Libnet to calculate this for us
  udp.payload  = payload
end

# build the IPv4 header
ip = l.build_ipv4 do |ip|
  ip.tos      = 0
  ip.length   = Libnet::HL_IPV4 + Libnet::HL_UDP + payload.length
  ip.id       = 0x4321
  ip.frag_off = 0
  ip.ttl      = 72
  ip.protocol = Libnet::IPPROTO_UDP
  ip.checksum = 0  # set to 0 to instruct Libnet to calculate this for us
  ip.src_ip   = "192.168.1.101" # IP addresses can be assigned as a dotted-decimal
  ip.dst_ip   = 0xc0a80103      # string or a network byte ordered integer
end

# finally, build the ethernet header
src_mac = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66].pack("C*")
eth = l.build_ethernet do |eth|
  eth.dst  = "aa:bb:cc:dd:ee:ff" # MAC addresses can be assigned as a colon-
  eth.src  = src_mac             # separated hex string or a byte string
  eth.type = 0x0800
end

# display a hex dump of the resulting packet
packet = l.pack
puts "packet (#{packet.length} bytes):\n#{Libnet.hexdump(packet)}"

# write the packet to the wire
n = l.write
puts "wrote #{n} bytes"

# decode the packet back into header objects
eth = Libnet::Ethernet.decode(packet)
ip  = Libnet::IPv4.decode(packet[Libnet::HL_ETH .. -1])
udp = Libnet::UDP.decode(packet[Libnet::HL_ETH + Libnet::HL_IPV4 .. -1])

puts "eth.dst:      #{Libnet.hexdump(eth.dst)}"
puts "eth.src:      #{Libnet.hexdump(eth.src)}"
puts "eth.type:     0x%04x" % [ eth.type ]
puts "ip.version:   #{ip.version}"
puts "ip.ihl:       #{ip.ihl}"
puts "ip.tos:       #{ip.tos}"
puts "ip.length:    #{ip.length}"
puts "ip.id:        0x%04x" % [ ip.id ]
puts "ip.frag_off:  #{ip.frag_off}"
puts "ip.ttl:       #{ip.ttl}"
puts "ip.protocol:  #{ip.protocol}"
puts "ip.checksum:  0x%04x" % [ ip.checksum ]
puts "ip.src_ip:    #{Libnet.ipv4_ntoa(ip.src_ip)}"
puts "ip.dst_ip:    #{Libnet.ipv4_ntoa(ip.dst_ip)}"
puts "udp.src_port: 0x%04x" % [ udp.src_port ]
puts "udp.dst_port: 0x%04x" % [ udp.dst_port ]
puts "udp.checksum: 0x%04x" % [ udp.checksum ]
puts "udp.payload:\n#{Libnet.hexdump(udp.payload)}"

The above script results in the following output:

packet (242 bytes):
0x00000000: aabbccdd eeff1122 33445566 08004500
0x00000010: 00e44321 00004811 ab2fc0a8 0165c0a8
0x00000020: 0103dead beef00d0 c2ad57a2 7e436761
0x00000030: a7f58e9b 277d4218 9f5e918e dd8f2379
0x00000040: 18047ac6 245b2e25 19e89c01 69900551
0x00000050: c195a6e1 fd2eb200 0c9baed0 c50e220e
0x00000060: 9fe7c7c8 6d59cd01 3bdfef59 2d0b4768
0x00000070: 776b33a8 452adb2c 4fae4584 fcc9892f
0x00000080: 7fa57498 a6ab1f00 66590c6e 5145e37f
0x00000090: 330875dc 38b48709 a92ab231 212f411d
0x000000a0: 23cb0ed2 3fc18dee dc0fa417 a897557b
0x000000b0: 7ad9fad2 2e334bec 6d62bc06 2d1dfa1c
0x000000c0: 896c6182 d91570c5 11b69dc0 9472c32c
0x000000d0: 3ab32aa7 3f5e9771 3db6db39 1a5c34d5
0x000000e0: dfa308be 5716b343 3bfd0428 e4da4aac
0x000000f0: 9504
wrote 242 bytes
eth.dst:      0x00000000: aabbccdd eeff
eth.src:      0x00000000: 11223344 5566
eth.type:     0x0800
ip.version:   4
ip.ihl:       5
ip.tos:       0
ip.length:    228
ip.id:        0x4321
ip.frag_off:  0
ip.ttl:       72
ip.protocol:  17
ip.checksum:  0x002f
ip.src_ip:    192.168.1.101
ip.dst_ip:    192.168.1.3
udp.src_port: 0xdead
udp.dst_port: 0xbeef
udp.checksum: 0xc2ad
udp.payload:
0x00000000: 57a27e43 6761a7f5 8e9b277d 42189f5e
0x00000010: 918edd8f 23791804 7ac6245b 2e2519e8
0x00000020: 9c016990 0551c195 a6e1fd2e b2000c9b
0x00000030: aed0c50e 220e9fe7 c7c86d59 cd013bdf
0x00000040: ef592d0b 4768776b 33a8452a db2c4fae
0x00000050: 4584fcc9 892f7fa5 7498a6ab 1f006659
0x00000060: 0c6e5145 e37f3308 75dc38b4 8709a92a
0x00000070: b231212f 411d23cb 0ed23fc1 8deedc0f
0x00000080: a417a897 557b7ad9 fad22e33 4bec6d62
0x00000090: bc062d1d fa1c896c 6182d915 70c511b6
0x000000a0: 9dc09472 c32c3ab3 2aa73f5e 97713db6
0x000000b0: db391a5c 34d5dfa3 08be5716 b3433bfd
0x000000c0: 0428e4da 4aac9504

Supported Header Types

The Libnet C library provides builder functions for many different protocol headers. Libnet4r is still very much a work in progress and only supports a subset of the header types that Libnet does. Patches for additional header types are welcome!

Here is the list of currently supported header types:

  • Ethernet

  • VLAN (802.1q)

  • ARP

  • IPv4

  • IPv6

  • UDP

Notes

The Libnet C source code comes packaged with this library and is built along with the extension so it is not necessary for you to already have Libnet installed on your system. If you do have Libnet already installed, it will not be used, the Libnet that comes with this package will still be used as it contains several patches that the extension depends on.

The following changes have been made to the Libnet 1.1.2.1 source code in this package:

  • Fixed bug that incorrectly packed the traffic class and flow label fields of the IPv6 header.

  • Fixed checksum bug when IPv6 header is present.

  • Removed check for root user from libnet_init(). This check is now only done in the libnet_write* functions. This allows all of the header builder functions to be used by a normal user. Its only necesssary to be the root user if you wish to actually inject the packets that you construct.

Project Page

rubyforge.org/projects/libnet4r

Author

Corey Burrows ([email protected])