Basic NETLINK_FIREWALL support. Highlights need for struct alignment
This commit is contained in:
@@ -268,6 +268,8 @@ module Netlink
|
||||
# ... others to be added as required
|
||||
|
||||
# linux/if.h
|
||||
IFNAMSIZ = 16
|
||||
IFALIASZ = 256
|
||||
IFF_UP = 0x1
|
||||
IFF_BROADCAST = 0x2
|
||||
IFF_DEBUG = 0x4
|
||||
@@ -289,4 +291,22 @@ module Netlink
|
||||
IFF_ECHO = 0x40000
|
||||
IFF_VOLATILE = (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_ECHO|\
|
||||
IFF_MASTER|IFF_SLAVE|IFF_RUNNING|IFF_LOWER_UP|IFF_DORMANT)
|
||||
|
||||
# linux/netfilter.h
|
||||
NF_DROP = 0
|
||||
NF_ACCEPT = 1
|
||||
NF_STOLEN = 2
|
||||
NF_QUEUE = 3
|
||||
NF_REPEAT = 4
|
||||
NF_STOP = 5
|
||||
|
||||
# linux/netfilter_ipv4/ip_queue.h
|
||||
IPQ_COPY_NONE = 0
|
||||
IPQ_COPY_META = 1
|
||||
IPQ_COPY_PACKET = 2
|
||||
|
||||
IPQM_MODE = 17
|
||||
IPQM_VERDICT = 18
|
||||
IPQM_PACKET = 19
|
||||
IPQM_MAX = 20
|
||||
end
|
||||
|
80
lib/netlink/firewall.rb
Normal file
80
lib/netlink/firewall.rb
Normal file
@@ -0,0 +1,80 @@
|
||||
# This file implements the messages and methods for the NETLINK_FIREWALL
|
||||
# protocol.
|
||||
#
|
||||
# TODO: implement multiple queue support (NFQUEUE)
|
||||
|
||||
require 'netlink/nlsocket'
|
||||
require 'netlink/message'
|
||||
|
||||
module Netlink
|
||||
# struct ipq_mode_msg
|
||||
class IPQMode < Message
|
||||
code IPQM_MODE
|
||||
|
||||
field :value, :uchar # IPQ_*
|
||||
field_pad 1.size - 1 # FIXME! C structs need aligning
|
||||
field :range, :size_t
|
||||
# FIXME! Kernel enforces that IPQM_MODE messages must be at least
|
||||
# as large as IPQM_VERDICT messages.
|
||||
field_pad 16
|
||||
end
|
||||
|
||||
# struct ipq_packet_msg
|
||||
class IPQPacket < Message
|
||||
code IPQM_PACKET
|
||||
|
||||
field :packet_id, :ulong
|
||||
field :mark, :ulong
|
||||
field :timestamp_sec, :long
|
||||
field :timestamp_usec, :long
|
||||
field :hook, :uint
|
||||
field :indev_name, :pattern => "Z#{IFNAMSIZ}"
|
||||
field :outdev_name, :pattern => "Z#{IFNAMSIZ}"
|
||||
field :hw_protocol, :ns
|
||||
field :hw_type, :ushort
|
||||
field :hw_addrlen, :uchar
|
||||
field :hw_addr, :pattern => "a8"
|
||||
field_pad 1.size - 1 # FIXME!
|
||||
field :data_len, :size_t
|
||||
field :payload, :binary # TODO: clip to data_len
|
||||
end
|
||||
|
||||
# struct ipq_verdict_msg
|
||||
class IPQVerdict < Message
|
||||
code IPQM_VERDICT
|
||||
|
||||
field :value, :uint # NF_*
|
||||
field_pad 1.size - 4
|
||||
field :id, :ulong
|
||||
field :data_len, :size_t # TODO: auto set from payload.bytesize
|
||||
field :payload, :binary # optional replacement packet
|
||||
end
|
||||
|
||||
module Firewall
|
||||
class Socket < NLSocket
|
||||
def initialize(opt={})
|
||||
super(opt.merge(:protocol => Netlink::NETLINK_FIREWALL))
|
||||
end
|
||||
|
||||
# Set mode to IPQ_COPY_META to receive metadata only, IPQ_COPY_PACKET
|
||||
# to get packet content, or IPQ_COPY_NONE to disable receipt of packets.
|
||||
# size=0 means copy whole packet, but you can specify a limit instead.
|
||||
def set_mode(mode, size=0)
|
||||
send_request IPQM_MODE, IPQMode.new(:value=>mode, :range=>size)
|
||||
end
|
||||
|
||||
# As packets are received they are yielded to the block. The block
|
||||
# must return one of the NF_* values, e.g. NF_ACCEPT/NF_DROP.
|
||||
# nil is treated as NF_ACCEPT.
|
||||
def dequeue_packets #:yields: pkt
|
||||
receive_stream(IPQM_PACKET) do |pkt|
|
||||
verdict = (yield pkt) || NF_ACCEPT
|
||||
send_request IPQM_VERDICT, IPQVerdict.new(
|
||||
:value => verdict,
|
||||
:id => pkt.packet_id
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -5,6 +5,9 @@ module Netlink
|
||||
EMPTY_STRING = "".freeze #:nodoc:
|
||||
EMPTY_ARRAY = [].freeze #:nodoc:
|
||||
|
||||
NLMSGHDR_PACK = "LSSLL".freeze # :nodoc:
|
||||
NLMSGHDR_SIZE = [0,0,0,0,0].pack(NLMSGHDR_PACK).bytesize # :nodoc:
|
||||
|
||||
# This is the base class from which all Netlink messages are derived.
|
||||
# To define a new Netlink message, make a subclass and then call the
|
||||
# "field" metaprogramming method to define the parts of the message, in
|
||||
@@ -61,6 +64,19 @@ module Netlink
|
||||
define_type :short, :pattern => "s_"
|
||||
define_type :int, :pattern => "i"
|
||||
define_type :long, :pattern => "l_"
|
||||
define_type :ns, :pattern => "n"
|
||||
define_type :nl, :pattern => "N"
|
||||
|
||||
SIZE_T_SIZE = Integer(`echo __SIZEOF_SIZE_T__ | gcc -E -P -`) rescue 1.size
|
||||
define_type :size_t,
|
||||
case SIZE_T_SIZE
|
||||
when 8
|
||||
{:pattern => "Q"}
|
||||
when 4
|
||||
{:pattern => "L"}
|
||||
else
|
||||
raise "Bad size_t"
|
||||
end
|
||||
define_type :binary, :pattern => "a*", :default => EMPTY_STRING
|
||||
# cstring has \x00 terminator when sent over wire
|
||||
define_type :cstring, :pattern => "Z*", :default => EMPTY_STRING
|
||||
@@ -181,6 +197,11 @@ module Netlink
|
||||
end
|
||||
end
|
||||
|
||||
# Skip pad byte(s) - default 1
|
||||
def self.field_pad(count=nil)
|
||||
self::FORMAT << "x#{count}" if count != 0
|
||||
end
|
||||
|
||||
# Returns the packed binary representation of this message (without
|
||||
# header, and not padded to NLMSG_ALIGNTO bytes)
|
||||
def to_s
|
||||
@@ -308,4 +329,17 @@ module Netlink
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# struct nlmsgerr
|
||||
class Err < Message
|
||||
code NLMSG_ERROR
|
||||
|
||||
field :error, :int
|
||||
#field :msg, :pattern => NLMSGHDR_PACK
|
||||
field :msg_len, :uint32
|
||||
field :msg_type, :uint16
|
||||
field :msg_flags, :uint16
|
||||
field :msg_seq, :uint32
|
||||
field :msg_pid, :uint32
|
||||
end
|
||||
end
|
||||
|
@@ -3,6 +3,13 @@ require 'netlink/constants'
|
||||
require 'netlink/message'
|
||||
|
||||
module Netlink
|
||||
ERRNO_MAP = {} #:nodoc:
|
||||
Errno.constants.each do |k|
|
||||
klass = Errno.const_get(k)
|
||||
next unless klass.is_a?(Class) and Class.const_defined?(:Errno)
|
||||
ERRNO_MAP[klass::Errno] = klass
|
||||
end
|
||||
|
||||
# NLSocket provides low-level sending and receiving of messages across
|
||||
# a netlink socket, adding headers to sent messages and parsing
|
||||
# received messages.
|
||||
@@ -76,9 +83,6 @@ module Netlink
|
||||
)
|
||||
end
|
||||
|
||||
NLMSGHDR_PACK = "LSSLL".freeze # :nodoc:
|
||||
NLMSGHDR_SIZE = [0,0,0,0,0].pack(NLMSGHDR_PACK).bytesize # :nodoc:
|
||||
|
||||
# Build a message comprising header+body. It is not padded at the end.
|
||||
def build_message(type, body, flags=NLM_F_REQUEST, seq=next_seq, pid=@pid)
|
||||
body = body.to_s
|
||||
@@ -126,7 +130,7 @@ module Netlink
|
||||
res = []
|
||||
blk ||= lambda { |msg| res << msg }
|
||||
junk_handler ||= lambda { |type, flags, seq, pid, msg|
|
||||
warn "Discarding junk message (#{type}) #{msg}" } if $VERBOSE
|
||||
warn "Discarding junk message (#{type}, #{flags}, #{seq}, #{pid}) #{msg.inspect}" } if $VERBOSE
|
||||
loop do
|
||||
receive_response(timeout) do |type, flags, seq, pid, msg|
|
||||
if pid != @pid || seq != @seq
|
||||
@@ -137,7 +141,7 @@ module Netlink
|
||||
when NLMSG_DONE
|
||||
return res
|
||||
when NLMSG_ERROR
|
||||
raise "Netlink Error received"
|
||||
raise ERRNO_MAP[-msg.error] || "Netlink Error: #{msg.inspect}"
|
||||
end
|
||||
if expected_type && type != expected_type
|
||||
junk_handler[type, flags, seq, pid, msg] if junk_handler
|
||||
@@ -147,6 +151,21 @@ module Netlink
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Loop infinitely receiving messages of given type(s), ignoring pid and seq.
|
||||
def receive_stream(*expected_type)
|
||||
loop do
|
||||
receive_response(nil) do |type, flags, seq, pid, msg|
|
||||
if expected_type.include?(type)
|
||||
yield msg
|
||||
elsif type == NLMSG_ERROR
|
||||
raise ERRNO_MAP[-msg.error] || "Netlink Error: #{msg.inspect}"
|
||||
else
|
||||
warn "Received unexpected message type #{type}: #{msg.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Receive one datagram from kernel. Yield header fields plus
|
||||
# Netlink::Message objects (maybe multiple times if the datagram
|
||||
|
@@ -26,7 +26,7 @@ module Netlink
|
||||
code RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK
|
||||
|
||||
field :family, :uchar # Socket::AF_*
|
||||
field :pad, :uchar
|
||||
field_pad
|
||||
field :type, :ushort # ARPHRD_*
|
||||
field :index, :int
|
||||
field :flags, :uint # IFF_*
|
||||
@@ -149,7 +149,7 @@ module Netlink
|
||||
#
|
||||
# res = nl.read_links
|
||||
# p res
|
||||
# [#<Netlink::IFInfo {:family=>0, :pad=>0, :type=>772, :index=>1,
|
||||
# [#<Netlink::IFInfo {:family=>0, :type=>772, :index=>1,
|
||||
# :flags=>65609, :change=>0, :ifname=>"lo", :txqlen=>0, :operstate=>0,
|
||||
# :linkmode=>0, :mtu=>16436, :qdisc=>"noqueue", :map=>"...",
|
||||
# :address=>"\x00\x00\x00\x00\x00\x00", :broadcast=>"\x00\x00\x00\x00\x00\x00",
|
||||
|
Reference in New Issue
Block a user