Basic interface address manipulation

This commit is contained in:
Brian Candler
2011-05-02 22:52:47 +01:00
parent ddba99c3b3
commit c551e805e6
3 changed files with 148 additions and 47 deletions

20
examples/add_addr.rb Normal file
View File

@@ -0,0 +1,20 @@
LIBDIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift LIBDIR
require 'netlink/route'
nl = Netlink::Route::Socket.new
puts "\n*** Before adding address"
nl.addrs["lo"][Socket::AF_INET].each { |x| puts x.address }
begin
nl.add_addr(:index=>"lo", :local=>"1.2.3.4", :prefixlen=>32)
rescue Errno::EEXIST
end
puts "\n*** After adding address"
nl.addrs["lo"][Socket::AF_INET].each { |x| puts x.address }
nl.delete_addr(:index=>"lo", :local=>"1.2.3.4", :prefixlen=>32)
puts "\n*** After deleting address"
nl.addrs["lo"][Socket::AF_INET].each { |x| puts x.address }

View File

@@ -48,6 +48,7 @@ module Netlink
# :seq => N (override initial sequence number)
# :pid => N (override PID)
# :timeout => N (seconds, default to DEFAULT_TIMEOUT. Pass nil for no timeout)
# :junk_handler => lambda { ... } for unexpected packets
def initialize(opt)
@socket ||= opt[:socket] || ::Socket.new(
Socket::AF_NETLINK,
@@ -58,6 +59,13 @@ module Netlink
@seq = opt[:seq] || Time.now.to_i
@pid = opt[:pid] || $$
@timeout = opt.has_key?(:timeout) ? opt[:timeout] : DEFAULT_TIMEOUT
if opt.has_key?(:junk_handler)
@junk_handler = opt[:junk_handler]
elsif $VERBOSE
@junk_handler = lambda { |type, flags, seq, pid, msg|
warn "Discarding junk message (#{type}, #{flags}, #{seq}, #{pid}) #{msg.inspect}"
}
end
end
# Generate the next sequence number
@@ -108,6 +116,18 @@ module Netlink
end
end
# Send a command and wait for an Errno::NOERROR as confirmation. Raise
# an exception if any error message is returned, or on timeout.
#
# (Compare: rtnl_talk in lib/libnetlink.c, with answer=NULL)
def cmd(type, msg, flags=NLM_F_REQUEST, timeout=@timeout, sockaddr=SOCKADDR_DEFAULT)
send_request(type, msg, flags|NLM_F_ACK, sockaddr)
receive_responses(true, timeout) do |type,msg|
return if type == NLMSG_ERROR
false
end
end
# Discard all waiting messages
def drain
while select([@socket], nil, nil, 0)
@@ -117,57 +137,72 @@ module Netlink
end
# Loop receiving responses until a DONE message is received (or you
# break out of the loop, or a timeout exception occurs). Filters out
# messages with unexpected pid and seq. If you pass an expected_type then
# messages other than this type will be discarded too.
# break out of the loop, or a timeout exception occurs).
#
# Yields Netlink::Message objects, or if no block is given, returns an
# array of those objects. If you provide a junk_handler then it will be
# called for discarded messages.
# array of those objects.
#
# (Compare: rtnl_dump_filter_l in lib/libnetlink.c)
def receive_until_done(expected_type=nil, timeout=@timeout, junk_handler=nil, &blk) #:yields: msg
def receive_until_done(expected_type=nil, timeout=@timeout, &blk) #:yields: msg
res = []
blk ||= lambda { |msg| res << msg }
junk_handler ||= lambda { |type, flags, seq, pid, msg|
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
junk_handler[type, flags, seq, pid, msg] if junk_handler
next
end
case type
when NLMSG_DONE
return res
when NLMSG_ERROR
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
next
end
blk ||= lambda { |obj| res << obj }
receive_responses(true, timeout) do |type,msg|
return res if type == NLMSG_DONE
if expected_type && type != expected_type
false
else
blk.call(msg) if msg
end
end
end
# Loop infinitely receiving messages of given type(s), ignoring pid and seq.
# Raises an exception on NLMSG_ERROR.
def receive_stream(*expected_types)
loop do
receive_response(nil) do |type, flags, seq, pid, msg|
if expected_types.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
# Loop infinitely receiving responses and yielding message objects
# of the given type.
def receive_stream(expected_type=nil)
receive_responses(false, nil) do |type, msg|
if expected_type && type != expected_type
false
else
yield msg
end
end
end
# This is the main loop for receiving responses. It optionally checks
# the pid/seq of received messages, and discards those which don't match.
# Raises an exception on NLMSG_ERROR.
#
# Matching messages are yielded to the block. If the block returns
# false then they are treated as junk.
def receive_responses(check_pid_seq=false, timeout=nil)
loop do
receive_response(timeout) do |type, flags, seq, pid, msg|
if !check_pid_seq || (pid == @pid && seq == @seq)
if type == NLMSG_ERROR && -msg.error != Errno::NOERROR::Errno
raise ERRNO_MAP[-msg.error] || "Netlink Error: #{msg.inspect}"
end
res = yield type, msg
next unless res == false
end
@junk_handler[type, flags, seq, pid, msg] if @junk_handler
end
end
end
# Receive one datagram from kernel. Validates the sender, and returns
# the raw binary message. Raises an exception on timeout or if the
# kernel closes the socket.
def recvmsg(timeout=@timeout)
if select([@socket], nil, nil, timeout)
mesg, sender, rflags, controls = @socket.recvmsg
raise EOFError unless mesg
NLSocket.check_sockaddr(sender.to_sockaddr)
mesg
else
raise "Timeout"
end
end
# Receive one datagram from kernel. Yield header fields plus
# Netlink::Message objects (maybe multiple times if the datagram
# includes multiple netlink messages). Raise an exception if no
@@ -176,23 +211,16 @@ module Netlink
#
# receive_response { |type, flags, seq, pid, msg| p msg }
def receive_response(timeout=@timeout, &blk) # :yields: type, flags, seq, pid, Message
if select([@socket], nil, nil, timeout)
mesg, sender, rflags, controls = @socket.recvmsg
raise EOFError unless mesg
NLSocket.check_sockaddr(sender.to_sockaddr)
parse_yield(mesg, &blk)
else
raise "Timeout"
end
parse_yield(recvmsg(timeout), &blk)
end
# Parse netlink packet in a string buffer. Yield header fields plus
# a Netlink::Message-derived object for each message. For unknown message
# types it will yield a raw String, or nil if there is no message body.
def parse_yield(mesg) # :yields: type, flags, seq, pid, Message-or-nil
dechunk(mesg) do |h_type, h_flags, h_seq, h_pid, data|
klass = Message::CODE_TO_MESSAGE[h_type]
yield h_type, h_flags, h_seq, h_pid,
dechunk(mesg) do |type, flags, seq, pid, data|
klass = Message::CODE_TO_MESSAGE[type]
yield type, flags, seq, pid,
if klass
klass.parse(data)
elsif data && data != EMPTY_STRING

View File

@@ -136,7 +136,7 @@ module Netlink
end
module Route
# This is the medium and high-level API using a NETLINK_ROUTE protocol socket
# This class formats and receives messages using NETLINK_ROUTE protocol
class Socket < NLSocket
def initialize(opt={})
super(opt.merge(:protocol => Netlink::NETLINK_ROUTE))
@@ -208,10 +208,48 @@ module Netlink
# 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.default = EMPTY_ARRAY
res
end
# Add an IP address to an interface
#
# require 'netlink/route'
# rt = Netlink::Route::Socket.new
# rt.add_ipaddr(:index=>"eth0", :local=>"1.2.3.4", :prefixlen=>24)
def add_addr(opt)
ipaddr_modify(RTM_NEWADDR, NLM_F_CREATE|NLM_F_EXCL, opt)
end
def change_addr(opt)
ipaddr_modify(RTM_NEWADDR, NLM_F_REPLACE, opt)
end
def replace_addr(opt)
ipaddr_modify(RTM_NEWADDR, NLM_F_CREATE|NLM_F_REPLACE, opt)
end
# Delete an IP address from an interface. Pass in either a hash of
# parameters, or an existing IFAddr object.
def delete_addr(opt)
ipaddr_modify(RTM_DELADDR, 0, opt)
end
def ipaddr_modify(code, flags, msg) #:nodoc:
msg = IFAddr.new(msg)
case msg.index
when nil
raise "Device index must be specified"
when String
msg.index = linkindex(msg.index)
end
msg.address ||= msg.local
# Note: IPAddr doesn't support addresses off the subnet base,
# so there's no point trying to set msg.prefixlen from the IPAddr mask
cmd code, msg, flags|NLM_F_REQUEST
clear_cache
end
# Clear the memoization cache
def clear_cache
@links = nil
@@ -238,6 +276,21 @@ module Netlink
)
end
# Convert a link index to a (String) name, or nil.
#
# rt.routes[Socket::AF_INET].each do |route|
# puts "iif=#{rt.linkname(route.iif)}"
# puts "oif=#{rt.linkname(route.oif)}"
# end
def linkname(x)
link[x] && link[x].ifname
end
# Convert a link name to an (Integer) index, or nil.
def linkindex(x)
link[x] && link[x].index
end
# Return the memoized address table, keyed by interface name and
# address family, containing an array of addresses for each
# interface/family combination. i.e.