Files
netlinkrb/lib/linux/iptables.rb
Brian Candler 40fbefa06b Experiment: try using FFI::Struct for iptables instead of CStruct
FFI::Struct handles nested structs and nested arrays much better, and avoids
duplicating logic about structure alignment (which it probably does more
correctly that CStruct)

However it's awkward to use in other ways. e.g. no accessor methods;
no proper #inspect; no ntohl for in_addr; no zero-sized arrays at end
of struct; no hooks to convert int32 <-> IPAddr as far as I can see.
2011-05-06 17:01:50 +01:00

126 lines
3.5 KiB
Ruby

require 'socket'
require 'linux/constants'
require 'ffi'
# Good things about FFI::Struct:
# - robust pre-existing code
# - good handling of nested structures and nested arrays
# Bad things about FFI::Struct:
# - no Hash initialization: MyStruct.new(:foo=>1, :bar=>2)
# - no accessor methods m.foo = 1
# - can't do zero size array at end of struct: layout :foo, [Foo, 0]
# - no network-order fields? (in_addr)
# - no decent inspect (fix this below)
class FFI::Struct
def inspect
res = "#<#{self.class}"
members.zip(values).each do |m,v|
res << " #{m}=#{v.inspect}"
end
res << ">"
end
end
class FFI::StructLayout::CharArray
def inspect
to_s.inspect
end
end
module Linux
module Ext
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")
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 self.tables
proc_read(self::PROC_TABLES)
end
def self.targets
proc_read(self::PROC_TARGETS)
end
def self.matches
proc_read(self::PROC_MATCHES)
end
def self.proc_read(filename)
File.readlines(filename).each { |x| x.chomp! }
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.size == 4
buflen.put_uint32(0, buf.size)
elsif buflen.size == 8
buflen.put_uint64(0, buf.size)
else
raise "Unexpected buflen length: #{buflen.size}"
end
res = Ext.getsockopt(@socket.fileno, level, optname, buf, buflen)
raise "getsockopt error: #{res}" if res < 0 # FIXME: get errno?
res # unlike Ruby's getsockopt, we return the length, not the buf
end
def reload
info = IPTGetInfo.new
info[:name] = @name
getsockopt(self.class::TC_IPPROTO, self.class::SO_GET_INFO, info)
#warn "valid_hooks=0x%08x, num_entries=%d, size=%d" % [info[:valid_hooks], info[:num_entries], info[:size]]
init = self.class::STRUCT_GET_ENTRIES.new
init[:name] = @name
init[:size] = info[:size]
buf2 = FFI::MemoryPointer.new(self.class::STRUCT_GET_ENTRIES_SIZE + info[:size])
buf2.put_bytes(0, init.pointer.get_bytes(0, self.class::STRUCT_GET_ENTRIES_SIZE))
getsockopt(self.class::TC_IPPROTO, self.class::SO_GET_ENTRIES, buf2)
res = []
offset = self.class::STRUCT_GET_ENTRIES_SIZE
limit = offset + info[:size]
while offset < limit
res << self.class::STRUCT_ENTRY.new(buf2 + offset)
offset += 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