Experimental code for reading iptables
This commit is contained in:
@@ -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
86
lib/linux/iptables.rb
Normal 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
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user