
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.
126 lines
3.5 KiB
Ruby
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
|