160 lines
4.3 KiB
Ruby
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
|