From ddba99c3b3c61360fc3d1e1ce3bde16bb2e38150 Mon Sep 17 00:00:00 2001 From: Brian Candler Date: Mon, 2 May 2011 20:27:05 +0100 Subject: [PATCH] Add NETFILTER_NFLOG (ULOG) support --- examples/nflog.rb | 14 ++++++++++++ lib/netlink/c_struct.rb | 6 ++++++ lib/netlink/constants.rb | 4 ++++ lib/netlink/firewall.rb | 10 ++++++--- lib/netlink/message.rb | 8 ++++++- lib/netlink/nflog.rb | 46 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 examples/nflog.rb create mode 100644 lib/netlink/nflog.rb diff --git a/examples/nflog.rb b/examples/nflog.rb new file mode 100644 index 0000000..6a2ff78 --- /dev/null +++ b/examples/nflog.rb @@ -0,0 +1,14 @@ +LIBDIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +$LOAD_PATH.unshift LIBDIR + +require 'pp' +require 'netlink/nflog' + +# Example of using Netlink::NFLog to capture all outbound packets +# to TCP port 7551. Use "telnet 127.0.0.1 7551" to test. + +#system("iptables -I OUTPUT -j ULOG --ulog-nlgroup 1 -p tcp --destination-port 7551") +nl = Netlink::NFLog::Socket.new(:group => 1) +nl.dequeue_packets do |pkt| + p pkt +end diff --git a/lib/netlink/c_struct.rb b/lib/netlink/c_struct.rb index 0e1db8d..eb40eaa 100644 --- a/lib/netlink/c_struct.rb +++ b/lib/netlink/c_struct.rb @@ -101,6 +101,11 @@ class CStruct end end + # This hook is called after unpacking from binary, and can be used + # for fixing up the data + def after_parse + end + def to_hash @attrs end @@ -177,6 +182,7 @@ class CStruct data.unpack(self::FORMAT).zip(self::FIELDS).each do |val, key| obj[key] = val end + obj.after_parse obj end diff --git a/lib/netlink/constants.rb b/lib/netlink/constants.rb index aec7952..8841763 100644 --- a/lib/netlink/constants.rb +++ b/lib/netlink/constants.rb @@ -309,4 +309,8 @@ module Netlink IPQM_VERDICT = 18 IPQM_PACKET = 19 IPQM_MAX = 20 + + # linux/netfilter_ipv4/ipt_ULOG.h + ULOG_MAC_LEN = 80 + ULOG_PREFIX_LEN = 32 end diff --git a/lib/netlink/firewall.rb b/lib/netlink/firewall.rb index dff58f4..88a225e 100644 --- a/lib/netlink/firewall.rb +++ b/lib/netlink/firewall.rb @@ -16,14 +16,18 @@ module Netlink field :timestamp_sec, :long field :timestamp_usec, :long field :hook, :uint - field :indev_name, :pattern => "Z#{IFNAMSIZ}", :default => EMPTY_STRING - field :outdev_name, :pattern => "Z#{IFNAMSIZ}", :default => EMPTY_STRING + field :indev_name, :dev_name + field :outdev_name, :dev_name field :hw_protocol, :ns field :hw_type, :ushort field :hw_addrlen, :uchar field :hw_addr, :pattern => "a8", :default => EMPTY_STRING field :data_len, :size_t - field :payload, :binary # TODO: clip to data_len + field :payload, :binary + + def after_parse #:nodoc: + payload.slice!(data_len..-1) if payload.length > data_len + end end # struct ipq_verdict_msg diff --git a/lib/netlink/message.rb b/lib/netlink/message.rb index be8701f..6293dfd 100644 --- a/lib/netlink/message.rb +++ b/lib/netlink/message.rb @@ -18,11 +18,15 @@ module Netlink # Use RtattrMessage instead for messages which are followed by variable rtattrs. class Message < CStruct # Map of numeric message type code => message class + # (TODO: should these be scoped to NETLINK_* protocol id?) CODE_TO_MESSAGE = {} # Define which message type code(s) to build using this structure def self.code(*codes) - codes.each { |code| CODE_TO_MESSAGE[code] = self } + codes.each do |code| + warn "Duplicate message code: #{code}" if CODE_TO_MESSAGE[code] + CODE_TO_MESSAGE[code] = self + end end NLMSG_ALIGNTO_1 = NLMSG_ALIGNTO-1 #:nodoc: @@ -51,6 +55,8 @@ module Netlink # specify :pack and :unpack lambdas to do higher-level conversion # of field values. class RtattrMessage < Message + define_type :dev_name, :pattern=>"Z#{IFNAMSIZ}", :default=>EMPTY_STRING + # L2 addresses are presented as ASCII hex. You may optionally include # colons, hyphens or dots. # IFInfo.new(:address => "00:11:22:33:44:55") # this is OK diff --git a/lib/netlink/nflog.rb b/lib/netlink/nflog.rb new file mode 100644 index 0000000..bd52ecc --- /dev/null +++ b/lib/netlink/nflog.rb @@ -0,0 +1,46 @@ +require 'netlink/message' +require 'netlink/nlsocket' + +module Netlink + ULOG_NL_EVENT = 111 # from ipv4/netfilter/ipt_ULOG.c + + # struct ulog_packet_msg + class UlogPacket < Message + code ULOG_NL_EVENT + + field :mark, :ulong + field :timestamp_sec, :long + field :timestamp_usec, :long + field :hook, :uint + field :indev_name, :dev_name + field :outdev_name, :dev_name + field :data_len, :size_t + field :prefix, :pattern=>"Z#{ULOG_PREFIX_LEN}", :default=>EMPTY_STRING + field :mac_len, :uchar + field :mac, :pattern=>"a#{ULOG_MAC_LEN}", :default=>EMPTY_STRING + field :payload, :binary + + def after_parse #:nodoc: + mac.slice!(mac_len..-1) if mac.length > mac_len + payload.slice!(data_len..-1) if payload.length > mac_len + end + end + + module NFLog + class Socket < NLSocket + # Create a socket to listen for ulog packets. You must pass :group=>N + # (where N is 1 to 32) or :groups=>bitmap to listen on multiple groups + def initialize(opt={}) + unless opt[:groups] + opt[:groups] = 1 << (opt.fetch(:group) - 1) + end + super(opt.merge(:protocol => Netlink::NETLINK_NFLOG)) + end + + # Receive packets and yield them to the block + def dequeue_packets(&blk) + receive_stream(ULOG_NL_EVENT, &blk) + end + end + end +end