From cf255395e12a356e9ef804232a9a2e351402f590 Mon Sep 17 00:00:00 2001 From: Brian Candler Date: Fri, 29 Apr 2011 18:28:37 +0100 Subject: [PATCH] Simple address/link/route memoization --- lib/netlink/nlsocket.rb | 6 +- lib/netlink/rtsocket.rb | 151 ++++++++++++++++++++++++++++------------ 2 files changed, 108 insertions(+), 49 deletions(-) diff --git a/lib/netlink/nlsocket.rb b/lib/netlink/nlsocket.rb index 7f2d42d..673d69b 100644 --- a/lib/netlink/nlsocket.rb +++ b/lib/netlink/nlsocket.rb @@ -109,9 +109,9 @@ module Netlink # are accepted. # # (Compare: rtnl_dump_filter_l in lib/libnetlink.c) - def receive_until_done(expect_type=nil, timeout=@timeout, junk_handler=nil, &blk) #:yields: type, flags, obj + def receive_until_done(expect_type=nil, timeout=@timeout, junk_handler=nil, &blk) #:yields: obj res = [] - blk ||= lambda { |type, flags, obj| res << obj if obj } + blk ||= lambda { |obj| res << obj } junk_handler ||= lambda { |type, flags, seq, pid, obj| warn "Discarding junk message (#{type}) #{obj}" } if $VERBOSE loop do @@ -130,7 +130,7 @@ module Netlink junk_handler[type, flags, seq, pid, obj] if junk_handler next end - blk.call(type, flags, obj) + blk.call(obj) if obj end end end diff --git a/lib/netlink/rtsocket.rb b/lib/netlink/rtsocket.rb index c5ddcbf..9779dfd 100644 --- a/lib/netlink/rtsocket.rb +++ b/lib/netlink/rtsocket.rb @@ -2,13 +2,16 @@ require 'netlink/nlsocket' require 'netlink/message' module Netlink - # This is the high-level API using a NETLINK_ROUTE protocol socket + # This is the medium and high-level API using a NETLINK_ROUTE protocol socket class RTSocket < NLSocket def initialize(opt={}) super(opt.merge(:protocol => Netlink::NETLINK_ROUTE)) + clear_cache end - # List links (interfaces). Returns an array of Netlink::Link objects. + # Download a list of links (interfaces). Either returns an array of + # Netlink::Link objects, or yields them to the supplied block. + # # res = nl.link_list # p res # [#0, :pad=>0, :type=>772, :index=>1, @@ -17,21 +20,20 @@ module Netlink # :address=>"\x00\x00\x00\x00\x00\x00", :broadcast=>"\x00\x00\x00\x00\x00\x00", # :stats=>#, # :stats64=>#}>, ...] - def links(opt=nil) + def read_links(opt=nil, &blk) send_request RTM_GETLINK, Link.new(opt), NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST - receive_until_done(RTM_NEWLINK) + receive_until_done(RTM_NEWLINK, &blk) end - # Return a Hash of Netlink::Link objects keyed by interface index, which - # is what the 'routes' and 'addrs' objects point to. - def links_by_index(opt=nil) - res = {} - links(opt).each { |obj| res[obj.index] = obj } - res - end - - # List routes. Returns an array of Netlink::Route objects + # Download a list of routes. Either returns an array of + # Netlink::Route objects, or yields them to the supplied block. + # + # A hash of kernel options may be supplied, but you might also have + # to perform your own filtering. e.g. + # rt.read_routes(:family=>Socket::AF_INET) # works + # rt.read_routes(:protocol=>Netlink::RTPROT_STATIC) # ignored + # # res = nl.routes(:family => Socket::AF_INET) # p res # [#2, :dst_len=>32, :src_len=>0, :tos=>0, @@ -45,38 +47,107 @@ module Netlink # [#2, :dst_len=>0, :src_len=>0, :tos=>0, # :table=>254, :protocol=>4, :scope=>0, :type=>1, :flags=>0, :table2=>254, # :gateway=>#, :oif=>2}>, ...] - def routes(opt=nil) + def read_routes(opt=nil, &blk) send_request RTM_GETROUTE, Route.new(opt), NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST - receive_until_done(RTM_NEWROUTE) + receive_until_done(RTM_NEWROUTE, &blk) end - # Return routes as a hash of {index=>[route,route], index=>[route,route]} - def routes_by_oif(opt=nil) - res = routes(opt).group_by { |obj| obj.oif } - res.default = [].freeze - res - end - - # List addresses. Return an array of Netlink::Addr objects. + # Download a list of link addresses. Either returns an array of + # Netlink::Addr objects, or yields them to the supplied block. # You will need to use the 'index' to cross reference to the interface. + # + # A hash of kernel options may be supplied, but likely only :family + # is honoured. + # # res = nl.addrs(:family => Socket::AF_INET) # p res # [#2, :prefixlen=>8, :flags=>128, :scope=>254, # :index=>1, :address=>#, # :local=>#, :label=>"lo"}>, ...] - def addrs(opt=nil) + def read_addrs(opt=nil, &blk) send_request RTM_GETADDR, Addr.new(opt), NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST - receive_until_done(RTM_NEWADDR) + receive_until_done(RTM_NEWADDR, &blk) end - # Return addresses as a hash of {index=>[addr,addr], index=>[addr,addr]} - def addrs_by_index(opt=nil) - res = addrs(opt).group_by { |obj| obj.index } + # Download a list of addresses, grouped as {index=>[addr,addr], index=>[addr,addr]} + def read_addrs_by_ifindex(opt=nil) + res = read_addrs(opt).group_by { |obj| obj.index } res.default = [].freeze res end + + # Clear the memoization cache + def clear_cache + @links = nil + @addrs = nil + @routes = nil + end + + # Return the memoized interface table, keyed by interface name. e.g. + # puts rt.links["eth0"].type + def links + @links ||= ( + res = {} + read_links.each { |obj| res[obj.ifname] = obj } + res + ) + end + + EMPTY_ARRAY = [].freeze #:nodoc: + + # Return the memoized address table, keyed by interface name and + # address family, containing an array of addresses for each + # interface/family combination. i.e. + # + # # {ifname=>{family=>[addr,addr,...], ...}, ...} + # puts rt.addrs["eth0"][Socket::AF_INET][0].address + # + # If there are no addresses for a particular family then it will + # return a (frozen) empty array, to make iteration eaiser. + def addrs + @addrs ||= ( + h = {} + index_to_link = {} + links.each do |name, link| + h[link.ifname] = {} + index_to_link[link.index] = link + end + read_addrs.each do |addr| + ifname = index_to_link[addr.index].ifname + h[ifname] ||= Hash.new(EMPTY_ARRAY) + (h[ifname][addr.family] ||= []) << addr + end + h + ) + end + + # Return the memoized route table, keyed by output interface name and + # address family, containing an array of routes for each interface/ + # family combination. i.e. + # + # # {ifname=>{family=>[route,route,...], ...}, ...} + # puts rt.routes["eth0"][Socket::AF_INET].first.dst + # + # If there are no routes for a particular family then it will + # return a (frozen) empty array, to make iteration eaiser. + def routes + @routes ||= ( + h = {} + index_to_link = {} + links.each do |name, link| + h[link.ifname] = {} + index_to_link[link.index] = link + end + read_routes.each do |route| + ifname = index_to_link[route.oif].ifname + h[ifname] ||= Hash.new(EMPTY_ARRAY) + (h[ifname][route.family] ||= []) << route + end + h + ) + end end end @@ -84,24 +155,12 @@ if __FILE__ == $0 require 'pp' nl = Netlink::RTSocket.new #puts "*** routes ***" - #pp nl.routes(:family => Socket::AF_INET) + #pp nl.read_routes(:family => Socket::AF_INET) #puts "*** links ***" - #pp nl.links(:family => Socket::AF_INET) + #pp nl.read_links #puts "*** addrs ***" - #pp nl.addrs(:family => Socket::AF_INET) - links = nl.links - addrs = nl.addrs_by_index(:family=>Socket::AF_UNSPEC) - routes = nl.routes_by_oif(:family=>Socket::AF_UNSPEC) - links.each do |link| - p link if $VERBOSE - puts "#{link.ifname}" - addrs[link.index].each do |addr| - p addr if $VERBOSE - puts " family=#{addr.family} #{addr.address}/#{addr.prefixlen} label=#{addr.label}" - end - routes[link.index].each do |route| - p route if $VERBOSE - puts " >> family=#{route.family} #{route.dst}/#{route.dst_len} gw=#{route.gateway}" - end - end + #pp nl.read_addrs(:family => Socket::AF_INET) + pp nl.links["eth0"] + pp nl.addrs["eth0"] + pp nl.routes["eth0"][Socket::AF_INET].min_by { |route| route.dst_len } end