Experimental code for reading iptables
This commit is contained in:
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
|
Reference in New Issue
Block a user