Files
netlinkrb/lib/linux/iptables.rb
2011-05-16 11:29:07 +01:00

160 lines
4.3 KiB
Ruby

require 'socket'
require 'linux/constants'
begin
require 'ffi'
rescue LoadError
require('rubygems') ? retry : raise
end
# 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 [*]
# [*] Fixed below in FFI::AStruct
class FFI::AStruct < FFI::Struct
# https://github.com/ffi/ffi/issues/102
def inspect
res = "#<#{self.class}"
members.zip(values).each do |m,v|
res << " #{m}=#{v.inspect}"
end
res << ">"
end
# https://github.com/ffi/ffi/issues/106
def initialize(*args)
if args.first.is_a? Hash
super(nil, *args[1..-1])
src.each { |k,v| self[k] = v }
else
super
end
end
# https://github.com/ffi/ffi/issues/107
def self.layout(*fields)
super
fields.each_slice(2) do |name,type|
# Structure building is screwed up if there's a 'size' member!
next if instance_methods.find { |m| m.to_sym == name }
define_method name do
self[name]
end
define_method "#{name}=" do |v|
self[name] = v
end
end
end
end
# https://github.com/ffi/ffi/issues/102
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