Experimental code for reading iptables

This commit is contained in:
Brian Candler
2011-05-06 11:41:06 +01:00
parent e07af9a315
commit 0cb1429c62
3 changed files with 114 additions and 27 deletions

View File

@@ -144,7 +144,7 @@ class CStruct
# Apply padding for structure alignment if necessary # Apply padding for structure alignment if necessary
size = info[:size] || [default].pack(pattern).bytesize size = info[:size] || [default].pack(pattern).bytesize
if align = info[:align] if align = (opt[:align] || info[:align])
align = size if align == true align = size if align == true
field_pad alignto(@bytesize, align) - @bytesize field_pad alignto(@bytesize, align) - @bytesize
end end

86
lib/linux/iptables.rb Normal file
View File

@@ -0,0 +1,86 @@
require 'socket'
require 'linux/constants'
require 'linux/c_struct'
require 'linux/netlink/message' # just for :dev_name type
module Linux
module Ext
require 'ffi'
extend FFI::Library
ffi_lib FFI::Library::LIBC
attach_function :getsockopt, [:int, :int, :int, :buffer_inout, :buffer_inout], :int
end
# *** THIS CODE IS INCOMPLETE ***
#
# Virtual base class for handling iptables. You should invoke either
# Iptables4 or Iptables6 as appropriate.
#
# (Mirrors the structure of iptables libiptc/libiptc.c, included by
# libip4tc.c and libip6tc.c)
#
# filter = Linux::Iptables4.table("filter")
#
# TODO: should we use ffi's structures instead of CStruct?
# We have to use ffi anyway, until ruby getsockopt supports buffer passing.
# http://redmine.ruby-lang.org/issues/4645
class Iptables
def self.inherited(subclass) #:nodoc:
subclass.instance_variable_set(:@tables, {})
end
def self.socket
@socket ||= Socket.new(self::TC_AF, Socket::SOCK_RAW, Socket::IPPROTO_RAW)
end
def self.table(tablename = "filter")
@tables[tablename] ||= new(tablename, socket)
end
def initialize(name, socket)
raise "Invalid table name" if name.bytesize > self.class::TABLE_MAXNAMELEN
@name = name
@socket = socket
reload
end
def rules
@rules
end
def getsockopt(level, optname, buf)
buflen = FFI::Buffer.new :socklen_t
if buflen.length == 4
buflen.put_uint32(0, buf.length)
elsif buflen.length == 8
buflen.put_uint64(0, buf.length)
else
raise "Unexpected buflen length: #{buflen.length}"
end
res = Ext.getsockopt(@socket.fileno, level, optname, buf, buflen)
raise "getsockopt error: #{res}" if res < 0 # FIXME: get errno?
buf.get_bytes(0, buflen.length == 4 ? buflen.get_uint32(0) : buflen.get_uint64(0))
end
def reload
buf = FFI::Buffer.new IPTGetInfo.bytesize
buf.put_string(0, @name)
info = self.class::STRUCT_GETINFO.parse(getsockopt(self.class::TC_IPPROTO, self.class::SO_GET_INFO, buf))
#warn "valid_hooks=0x%08x, num_entries=%d, size=%d" % [info.valid_hooks, info.num_entries, info.size]
buf2 = FFI::Buffer.new(self.class::STRUCT_GET_ENTRIES.bytesize + info.size)
buf2.put_bytes(0, self.class::STRUCT_GET_ENTRIES.new(:name=>@name, :size=>info.size).to_str)
getsockopt(self.class::TC_IPPROTO, self.class::SO_GET_ENTRIES, buf2)
res = []
ptr = self.class::STRUCT_GET_ENTRIES.bytesize
limit = ptr + info.size
while ptr < limit
res << self.class::STRUCT_ENTRY.parse(buf2.get_bytes(ptr, self.class::STRUCT_ENTRY.bytesize))
ptr += res.last.next_offset
end
raise "Error parsing rules: got #{res.size} instead of #{info.num_entries}" if res.size != info.num_entries
@rules = res
end
end
end

View File

@@ -1,7 +1,4 @@
require 'socket' require 'linux/iptables'
require 'linux/constants'
require 'linux/c_struct'
require 'linux/netlink/message' # just for :dev_name type
module Linux module Linux
#- #-
@@ -24,8 +21,7 @@ module Linux
class IPTGetEntries < CStruct class IPTGetEntries < CStruct
field :name, :pattern=>"Z#{IPT_TABLE_MAXNAMELEN}", :default=>EMPTY_STRING field :name, :pattern=>"Z#{IPT_TABLE_MAXNAMELEN}", :default=>EMPTY_STRING
field :size, :uint field :size, :uint
#field :entrytable, :pattern=> field :entrytable, :binary, :align=>1.size # struct ipt_entry entrytable[0]
field :entrytable, :binary # struct ipt_entry entrytable[]
end end
# struct ipt_entry # struct ipt_entry
@@ -43,34 +39,39 @@ module Linux
field :flags, :uchar field :flags, :uchar
field :invflags, :uchar field :invflags, :uchar
#### ####
field :nfcache, :int field :nfcache, :uint
field :target_offset, :uint16 field :target_offset, :uint16 # size of ipt_entry + matches
field :next_offset, :uint16 field :next_offset, :uint16 # size of ipt_entry + matches + target
field :comefrom, :uint field :comefrom, :uint
field :packet_count, :uint64 ### struct xt_counters
field :packet_count, :uint64, :align => 8
field :byte_count, :uint64 field :byte_count, :uint64
field :elems, :binary ###
field :elems, :binary # matches (if any), then the target
def after_parse
self.src = src == 0 ? nil : IPAddr.new(src, Socket::AF_INET)
self.dst = dst == 0 ? nil : IPAddr.new(dst, Socket::AF_INET)
self.smsk = smsk == 0 ? nil : IPAddr.new(smsk, Socket::AF_INET)
self.dmsk = dmsk == 0 ? nil : IPAddr.new(dmsk, Socket::AF_INET)
end
end end
# Class for handling iptables. Note that this doesn't actually use # Class for handling iptables. Note that this doesn't actually use
# Netlink at all :-( # Netlink at all :-(
class Iptables4 class Iptables4 < Iptables
TABLE_MAXNAMELEN = IPT_TABLE_MAXNAMELEN
TC_AF = Socket::AF_INET TC_AF = Socket::AF_INET
TC_IPPROTO = Socket::IPPROTO_IP TC_IPPROTO = Socket::IPPROTO_IP
SO_GET_INFO = IPT_SO_GET_INFO SO_GET_INFO = IPT_SO_GET_INFO
SO_GET_ENTRIES = IPT_SO_GET_ENTRIES
STRUCT_ENTRY = IPTEntry
STRUCT_GETINFO = IPTGetInfo STRUCT_GETINFO = IPTGetInfo
STRUCT_GET_ENTRIES = IPTGetEntries STRUCT_GET_ENTRIES = IPTGetEntries
def initialize(tablename = "filter")
@socket = Socket.new(TC_AF, Socket::SOCK_RAW, Socket::IPPROTO_RAW)
info = STRUCT_GETINFO.new(:name => tablename)
# FIXME: ruby won't let us pass structure to getsockopt!!
@socket.getsockopt(TC_IPPROTO, SO_GET_INFO, info)
warn "valid_hooks=0x%08x, num_entries=%d, size=%d" % [info.valid_hooks, info.size, info.num_entries]
end
end end
end end
if __FILE__ == $0 if __FILE__ == $0
iptables = Linux::Iptables4.new require 'pp'
pp Linux::Iptables4.table("filter").rules
end end