diff --git a/lib/netlink/constants.rb b/lib/netlink/constants.rb index 43f91d7..bb7fc16 100644 --- a/lib/netlink/constants.rb +++ b/lib/netlink/constants.rb @@ -1,6 +1,6 @@ require 'socket' class Socket - # From /usr/include/bits/socket.h + # From bits/socket.h PF_NETLINK = 16 unless defined? Socket::PF_NETLINK AF_NETLINK = PF_NETLINK unless defined? Socket::AF_NETLINK end @@ -145,5 +145,57 @@ module Netlink RTPROT_BOOT = 3 RTPROT_STATIC = 4 - # MORE TO GO + # XXXX MORE TO GO + + RTA_UNSPEC = 0 + RTA_DST = 1 + RTA_SRC = 2 + RTA_IIF = 3 + RTA_OIF = 4 + RTA_GATEWAY = 5 + RTA_PRIORITY = 6 + RTA_PREFSRC = 7 + RTA_METRICS = 8 + RTA_MULTIPATH = 9 + RTA_FLOW = 11 + RTA_CACHEINFO = 12 + RTA_TABLE = 15 + + # from linux/if_link.h + IFLA_UNSPEC = 0 + IFLA_ADDRESS = 1 + IFLA_BROADCAST = 2 + IFLA_IFNAME = 3 + IFLA_MTU = 4 + IFLA_LINK = 5 + IFLA_QDISC = 6 + IFLA_STATS = 7 + IFLA_COST = 8 + IFLA_PRIORITY = 9 + IFLA_MASTER = 10 + IFLA_WIRELESS = 11 + IFLA_PROTINFO = 12 + IFLA_TXQLEN = 13 + IFLA_MAP = 14 + IFLA_WEIGHT = 15 + IFLA_OPERSTATE = 16 + IFLA_LINKMODE = 17 + IFLA_LINKINFO = 18 + IFLA_NET_NS_PID = 19 + IFLA_IFALIAS = 20 + IFLA_NUM_VF = 21 + IFLA_VFINFO_LIST = 22 + IFLA_STATS64 = 23 + IFLA_VF_PORTS = 24 + IFLA_PORT_SELF = 25 + + # from linux/if_addr.h + IFA_UNSPEC = 0 + IFA_ADDRESS = 1 + IFA_LOCAL = 2 + IFA_LABEL = 3 + IFA_BROADCAST = 4 + IFA_ANYCAST = 5 + IFA_CACHEINFO = 6 + IFA_MULTICAST = 7 end diff --git a/lib/netlink/message.rb b/lib/netlink/message.rb index 8435cb6..e67e51b 100644 --- a/lib/netlink/message.rb +++ b/lib/netlink/message.rb @@ -6,16 +6,36 @@ module Netlink # Map of numeric message type code => message class CODE_TO_MESSAGE = {} + # Defines each of the possible field types + TYPE_INFO = { + :uchar => { :pattern => "C" }, + :uint16 => { :pattern => "S" }, + :uint32 => { :pattern => "L" }, + :char => { :pattern => "c" }, + :int16 => { :pattern => "s" }, + :int32 => { :pattern => "l" }, + :ushort => { :pattern => "S_" }, + :uint => { :pattern => "I" }, + :ulong => { :pattern => "L_" }, + :short => { :pattern => "s_" }, + :int => { :pattern => "i" }, + :long => { :pattern => "l_" }, + :binary => { :pattern => "a*", :default => "" }, + :cstring => { :pattern => "Z*", :default => "" }, + :stats32 => { :pattern => "L*", :default => [] }, + :stats64 => { :pattern => "Q*", :default => [] }, + } + # You can initialize a message from a Hash or from another # instance of itself. # # class Foo < Message - # field :foo, "C", 0xff - # field :bar, "L", 0 + # field :foo, :char, :default=>255 + # field :bar, :long # end # msg = Foo.new(:bar => 123) # or ("bar" => 123) # msg2 = Foo.new(msg) - # msg3 = Foo.new(:qux => 999) # error, no method qux= + # msg3 = Foo.new(:qux => 999) # error, no method "qux=" def initialize(h={}) if h.instance_of?(self.class) @attrs = h.to_hash.dup @@ -54,12 +74,13 @@ module Netlink codes.each { |code| CODE_TO_MESSAGE[code] = self } end - # Define a field for this message, which creates accessor methods. The - # "pattern" is the Array#pack or String#unpack code to extract this field. - def self.field(name, pattern, default=nil, opt={}) + # Define a field for this message, which creates accessor methods and + # sets up data required to pack and unpack the structure. + def self.field(name, type, opt={}) + info = TYPE_INFO[type] self::FIELDS << name - self::FORMAT << pattern - self::DEFAULTS[name] = default + self::FORMAT << info[:pattern] + self::DEFAULTS[name] = opt.fetch(:default) { info.fetch(:default, 0) } define_method name do @attrs.fetch name end @@ -68,19 +89,6 @@ module Netlink end end - def self.uchar(name, *args); field name, "C", 0, *args; end - def self.uint16(name, *args); field name, "S", 0, *args; end - def self.uint32(name, *args); field name, "L", 0, *args; end - def self.char(name, *args); field name, "c", 0, *args; end - def self.int16(name, *args); field name, "s", 0, *args; end - def self.int32(name, *args); field name, "l", 0, *args; end - def self.ushort(name, *args); field name, "S_", 0, *args; end - def self.uint(name, *args); field name, "I", 0, *args; end - def self.ulong(name, *args); field name, "L_", 0, *args; end - def self.short(name, *args); field name, "s_", 0, *args; end - def self.int(name, *args); field name, "i", 0, *args; end - def self.long(name, *args); field name, "l_", 0, *args; end - # Returns the packed binary representation of this message (without # header, and not padded to NLMSG_ALIGNTO bytes) def to_s @@ -92,9 +100,9 @@ module Netlink end # Convert a binary representation of this message into an object instance - def self.parse(str) + def self.parse(data) res = new - str.unpack(self::FORMAT).zip(self::FIELDS).each do |val, key| + data.unpack(self::FORMAT).zip(self::FIELDS).each do |val, key| res[key] = val end res @@ -114,38 +122,149 @@ module Netlink def self.pad(str) str << PADDING[0, align(str.bytesize) - str.bytesize] end - end - class Link < Message + # This is a class for a Message which is followed by Rtattr key/value pairs. + # We assume that any particular attribute is not repeated, so it maps to + # a single attribute in the underlying hash. + class RtattrMessage < Message + RTATTR_PACK = "S_S_".freeze #:nodoc: + RTATTR_SIZE = [0,0].pack(RTATTR_PACK).bytesize #:nodoc: + + def self.inherited(subclass) #:nodoc: + super + subclass.const_set(:RTATTRS, {}) + end + + def self.rtattr(name, code, type=nil, opt={}) + info = TYPE_INFO[type] + self::RTATTRS[code] = [name, info && info[:pattern]] + define_method name do + @attrs.fetch name + end + define_method "#{name}=" do |val| + @attrs.store name, val + end + end + + def self.attr_offset #:nodoc: + @attr_offset ||= Message.align(new.to_s.bytesize) + end + + def to_s + data = super + self.class::RTATTRS.each do |code, (name, pattern)| + if val = @attrs[name] + Message.pad(data) + val = Array(val).pack(pattern) if pattern + data << [val.bytesize+RTATTR_SIZE, code].pack(RTATTR_PACK) << val + end + end + data + end + + def self.parse(data) + res = super + ptr = attr_offset + while ptr < data.bytesize + raise "Truncated rtattr header!" if ptr + RTATTR_SIZE > data.bytesize + len, code = data[ptr, RTATTR_SIZE].unpack(RTATTR_PACK) + raise "Truncated rtattr body!" if ptr + len > data.bytesize + raise "Invalid rtattr len!" if len < RTATTR_SIZE + res._add_attr(code, data[ptr+RTATTR_SIZE, len-RTATTR_SIZE]) + ptr = Message.align(ptr + len) + end + res + end + + def _add_attr(code, val) # :nodoc: + name, pattern = self.class::RTATTRS[code] + if name + if pattern + val = val.unpack(pattern) + val = val.first if val.size == 1 + end + warn "Duplicate attribute #{name} (#{code}): #{@attrs[name].inspect} -> #{val.inspect}" if @attrs[name] + @attrs[name] = val + else + warn "Unknown attribute #{code}, value #{val.inspect}" + @attrs[code] = val + end + end + end + + class Link < RtattrMessage code RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK - uchar :family - uchar :pad - ushort :type - int :index - uint :flags - uint :change + field :family, :uchar + field :pad, :uchar + field :type, :ushort + field :index, :int + field :flags, :uint + field :change, :uint + rtattr :address, IFLA_ADDRESS + rtattr :broadcast, IFLA_BROADCAST + rtattr :ifname, IFLA_IFNAME, :cstring + rtattr :mtu, IFLA_MTU, :uint32 + rtattr :link, IFLA_LINK, :int32 + rtattr :qdisc, IFLA_QDISC, :cstring + rtattr :stats, IFLA_STATS, :stats32 + rtattr :cost, IFLA_COST + rtattr :master, IFLA_MASTER + rtattr :wireless, IFLA_WIRELESS + rtattr :protinfo, IFLA_PROTINFO + rtattr :txqlen, IFLA_TXQLEN, :uint32 + rtattr :map, IFLA_MAP + rtattr :weight, IFLA_WEIGHT + rtattr :operstate, IFLA_OPERSTATE, :uchar + rtattr :linkmode, IFLA_LINKMODE, :uchar + rtattr :linkinfo, IFLA_LINKINFO + rtattr :net_ns_pid, IFLA_NET_NS_PID + rtattr :ifalias, IFLA_IFALIAS + rtattr :num_vf, IFLA_NUM_VF, :uint32 + rtattr :vfinfo_list, IFLA_VFINFO_LIST + rtattr :stats64, IFLA_STATS64, :stats64 + rtattr :vf_ports, IFLA_VF_PORTS + rtattr :port_self, IFLA_PORT_SELF end - class Addr < Message + class Addr < RtattrMessage code RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR - uchar :family - uchar :prefixlen - uchar :flags - uchar :scope - int :index + field :family, :uchar + field :prefixlen, :uchar + field :flags, :uchar + field :scope, :uchar + field :index, :int + rtattr :address, IFA_ADDRESS + rtattr :local, IFA_LOCAL + rtattr :label, IFA_LABEL, :cstring + rtattr :broadcast, IFA_BROADCAST + rtattr :anycase, IFA_ANYCAST + rtattr :cacheinfo, IFA_CACHEINFO + rtattr :multicast, IFA_MULTICAST end - class Route < Message + class Route < RtattrMessage code RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE - uchar :family - uchar :dst_len - uchar :src_len - uchar :tos - uchar :table - uchar :protocol - uchar :scope - uchar :type - uint :flags + field :family, :uchar + field :dst_len, :uchar + field :src_len, :uchar + field :tos, :uchar + field :table, :uchar + field :protocol, :uchar + field :scope, :uchar + field :type, :uchar + field :flags, :uint + rtattr :dst, RTA_DST + rtattr :src, RTA_SRC + rtattr :iif, RTA_IIF, :uint32 + rtattr :oif, RTA_OIF, :uint32 + rtattr :gateway, RTA_GATEWAY + rtattr :priority, RTA_PRIORITY, :uint32 + rtattr :prefsrc, RTA_PREFSRC + rtattr :metrics, RTA_METRICS + rtattr :multipath, RTA_MULTIPATH + rtattr :flow, RTA_FLOW + rtattr :cacheinfo, RTA_CACHEINFO + rtattr :table2, RTA_TABLE, :uint32 # NOTE: table in two places! end end diff --git a/lib/netlink/nlsocket.rb b/lib/netlink/nlsocket.rb index facf229..b908851 100644 --- a/lib/netlink/nlsocket.rb +++ b/lib/netlink/nlsocket.rb @@ -7,7 +7,6 @@ module Netlink DEFAULT_TIMEOUT = 2 SOCKADDR_PACK = "SSLL".freeze #:nodoc: - SOCKADDR_SIZE = 12 # :nodoc: # Generate a sockaddr_nl. Pass :pid and/or :groups. def self.sockaddr(opt={}) @@ -71,7 +70,7 @@ module Netlink end NLMSGHDR_PACK = "LSSLL".freeze # :nodoc: - NLMSGHDR_SIZE = 16 # :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=(@seq += 1), pid=@pid) @@ -164,6 +163,7 @@ module Netlink len, type, flags, seq, pid = mesg[ptr,NLMSGHDR_SIZE].unpack(NLMSGHDR_PACK) STDERR.puts " len=#{len}, type=#{type}, flags=#{flags}, seq=#{seq}, pid=#{pid}" if $DEBUG raise "Truncated netlink message!" if ptr + len > mesg.bytesize + raise "Invalid netlink len!" if len < NLMSGHDR_SIZE data = mesg[ptr+NLMSGHDR_SIZE, len-NLMSGHDR_SIZE] STDERR.puts " data=#{data.inspect}" if $DEBUG && !data.empty? yield type, flags, seq, pid, data diff --git a/lib/netlink/rtsocket.rb b/lib/netlink/rtsocket.rb index 7f63030..e9a573a 100644 --- a/lib/netlink/rtsocket.rb +++ b/lib/netlink/rtsocket.rb @@ -47,7 +47,10 @@ end if __FILE__ == $0 require 'pp' nl = Netlink::RTSocket.new + puts "*** routes ***" pp nl.route_list(:family => Socket::AF_INET) + puts "*** links ***" pp nl.link_list(:family => Socket::AF_INET) + puts "*** addrs ***" pp nl.addr_list(:family => Socket::AF_INET) end