Basic interface address manipulation
This commit is contained in:
20
examples/add_addr.rb
Normal file
20
examples/add_addr.rb
Normal 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 }
|
||||||
|
|
@@ -48,6 +48,7 @@ module Netlink
|
|||||||
# :seq => N (override initial sequence number)
|
# :seq => N (override initial sequence number)
|
||||||
# :pid => N (override PID)
|
# :pid => N (override PID)
|
||||||
# :timeout => N (seconds, default to DEFAULT_TIMEOUT. Pass nil for no timeout)
|
# :timeout => N (seconds, default to DEFAULT_TIMEOUT. Pass nil for no timeout)
|
||||||
|
# :junk_handler => lambda { ... } for unexpected packets
|
||||||
def initialize(opt)
|
def initialize(opt)
|
||||||
@socket ||= opt[:socket] || ::Socket.new(
|
@socket ||= opt[:socket] || ::Socket.new(
|
||||||
Socket::AF_NETLINK,
|
Socket::AF_NETLINK,
|
||||||
@@ -58,6 +59,13 @@ module Netlink
|
|||||||
@seq = opt[:seq] || Time.now.to_i
|
@seq = opt[:seq] || Time.now.to_i
|
||||||
@pid = opt[:pid] || $$
|
@pid = opt[:pid] || $$
|
||||||
@timeout = opt.has_key?(:timeout) ? opt[:timeout] : DEFAULT_TIMEOUT
|
@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
|
end
|
||||||
|
|
||||||
# Generate the next sequence number
|
# Generate the next sequence number
|
||||||
@@ -108,6 +116,18 @@ module Netlink
|
|||||||
end
|
end
|
||||||
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
|
# Discard all waiting messages
|
||||||
def drain
|
def drain
|
||||||
while select([@socket], nil, nil, 0)
|
while select([@socket], nil, nil, 0)
|
||||||
@@ -117,57 +137,72 @@ module Netlink
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Loop receiving responses until a DONE message is received (or you
|
# Loop receiving responses until a DONE message is received (or you
|
||||||
# break out of the loop, or a timeout exception occurs). Filters out
|
# break out of the loop, or a timeout exception occurs).
|
||||||
# messages with unexpected pid and seq. If you pass an expected_type then
|
|
||||||
# messages other than this type will be discarded too.
|
|
||||||
#
|
#
|
||||||
# Yields Netlink::Message objects, or if no block is given, returns an
|
# 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
|
# array of those objects.
|
||||||
# called for discarded messages.
|
|
||||||
#
|
#
|
||||||
# (Compare: rtnl_dump_filter_l in lib/libnetlink.c)
|
# (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 = []
|
res = []
|
||||||
blk ||= lambda { |msg| res << msg }
|
blk ||= lambda { |obj| res << obj }
|
||||||
junk_handler ||= lambda { |type, flags, seq, pid, msg|
|
receive_responses(true, timeout) do |type,msg|
|
||||||
warn "Discarding junk message (#{type}, #{flags}, #{seq}, #{pid}) #{msg.inspect}" } if $VERBOSE
|
return res if type == NLMSG_DONE
|
||||||
loop do
|
if expected_type && type != expected_type
|
||||||
receive_response(timeout) do |type, flags, seq, pid, msg|
|
false
|
||||||
if pid != @pid || seq != @seq
|
else
|
||||||
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.call(msg) if msg
|
blk.call(msg) if msg
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Loop infinitely receiving messages of given type(s), ignoring pid and seq.
|
# 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.
|
# Raises an exception on NLMSG_ERROR.
|
||||||
def receive_stream(*expected_types)
|
#
|
||||||
|
# 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
|
loop do
|
||||||
receive_response(nil) do |type, flags, seq, pid, msg|
|
receive_response(timeout) do |type, flags, seq, pid, msg|
|
||||||
if expected_types.include?(type)
|
if !check_pid_seq || (pid == @pid && seq == @seq)
|
||||||
yield msg
|
if type == NLMSG_ERROR && -msg.error != Errno::NOERROR::Errno
|
||||||
elsif type == NLMSG_ERROR
|
raise ERRNO_MAP[-msg.error] || "Netlink Error: #{msg.inspect}"
|
||||||
raise ERRNO_MAP[-msg.error] || "Netlink Error: #{msg.inspect}"
|
end
|
||||||
else
|
res = yield type, msg
|
||||||
warn "Received unexpected message type #{type}: #{msg.inspect}"
|
next unless res == false
|
||||||
end
|
end
|
||||||
|
@junk_handler[type, flags, seq, pid, msg] if @junk_handler
|
||||||
end
|
end
|
||||||
end
|
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
|
# Receive one datagram from kernel. Yield header fields plus
|
||||||
# Netlink::Message objects (maybe multiple times if the datagram
|
# Netlink::Message objects (maybe multiple times if the datagram
|
||||||
# includes multiple netlink messages). Raise an exception if no
|
# includes multiple netlink messages). Raise an exception if no
|
||||||
@@ -176,23 +211,16 @@ module Netlink
|
|||||||
#
|
#
|
||||||
# receive_response { |type, flags, seq, pid, msg| p msg }
|
# receive_response { |type, flags, seq, pid, msg| p msg }
|
||||||
def receive_response(timeout=@timeout, &blk) # :yields: type, flags, seq, pid, Message
|
def receive_response(timeout=@timeout, &blk) # :yields: type, flags, seq, pid, Message
|
||||||
if select([@socket], nil, nil, timeout)
|
parse_yield(recvmsg(timeout), &blk)
|
||||||
mesg, sender, rflags, controls = @socket.recvmsg
|
|
||||||
raise EOFError unless mesg
|
|
||||||
NLSocket.check_sockaddr(sender.to_sockaddr)
|
|
||||||
parse_yield(mesg, &blk)
|
|
||||||
else
|
|
||||||
raise "Timeout"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse netlink packet in a string buffer. Yield header fields plus
|
# Parse netlink packet in a string buffer. Yield header fields plus
|
||||||
# a Netlink::Message-derived object for each message. For unknown message
|
# 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.
|
# 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
|
def parse_yield(mesg) # :yields: type, flags, seq, pid, Message-or-nil
|
||||||
dechunk(mesg) do |h_type, h_flags, h_seq, h_pid, data|
|
dechunk(mesg) do |type, flags, seq, pid, data|
|
||||||
klass = Message::CODE_TO_MESSAGE[h_type]
|
klass = Message::CODE_TO_MESSAGE[type]
|
||||||
yield h_type, h_flags, h_seq, h_pid,
|
yield type, flags, seq, pid,
|
||||||
if klass
|
if klass
|
||||||
klass.parse(data)
|
klass.parse(data)
|
||||||
elsif data && data != EMPTY_STRING
|
elsif data && data != EMPTY_STRING
|
||||||
|
@@ -136,7 +136,7 @@ module Netlink
|
|||||||
end
|
end
|
||||||
|
|
||||||
module Route
|
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
|
class Socket < NLSocket
|
||||||
def initialize(opt={})
|
def initialize(opt={})
|
||||||
super(opt.merge(:protocol => Netlink::NETLINK_ROUTE))
|
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]}
|
# Download a list of addresses, grouped as {index=>[addr,addr], index=>[addr,addr]}
|
||||||
def read_addrs_by_ifindex(opt=nil)
|
def read_addrs_by_ifindex(opt=nil)
|
||||||
res = read_addrs(opt).group_by { |obj| obj.index }
|
res = read_addrs(opt).group_by { |obj| obj.index }
|
||||||
res.default = [].freeze
|
res.default = EMPTY_ARRAY
|
||||||
res
|
res
|
||||||
end
|
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
|
# Clear the memoization cache
|
||||||
def clear_cache
|
def clear_cache
|
||||||
@links = nil
|
@links = nil
|
||||||
@@ -238,6 +276,21 @@ module Netlink
|
|||||||
)
|
)
|
||||||
end
|
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
|
# Return the memoized address table, keyed by interface name and
|
||||||
# address family, containing an array of addresses for each
|
# address family, containing an array of addresses for each
|
||||||
# interface/family combination. i.e.
|
# interface/family combination. i.e.
|
||||||
|
Reference in New Issue
Block a user