1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
require 'rethtool'
require 'rethtool/ethtool_cmd'
# All of the settings of a network interface. Ridiculous amounts of
# info is available; we only support a subset of them at present.
#
# Create an instance of this class with the interface name as the only
# parameter, then use the available instance methods to get the info you
# seek:
#
# if = Rethtool::InterfaceSettings.new("eth0")
# puts "Current link mode is #{if.current_mode}"
#
class Rethtool::InterfaceSettings
Mode = Struct.new(:speed, :duplex, :media)
# A struct to represent interface modes (supported, advertised, current)
#
# available fields are:
#
# .speed -- integer link speed, in Mb (-1 if unknown)
# .duplex -- :full, :half, :fec, or :unknown
# .media -- A string, such as 'T', 'X', 'KX', etc, or nil if unknown
#
class Mode
# Print out a more standard-looking representation for a mode
def to_s
if self.speed == :unknown
"Unknown"
else
"#{self.speed}base#{self.media}/#{self.duplex}"
end
end
end
# Create a new InterfaceSettings object. Simply pass it the name of the
# interface you want to get the settings for.
def initialize(interface)
@interface = interface
cmd = Rethtool::EthtoolCmd.new
cmd.cmd = Rethtool::ETHTOOL_CMD_GSET
@data = Rethtool.ioctl(interface, cmd)
end
# Return an array of the modes supported by the interface. Returns an
# array of Mode objects.
def supported_modes
modes(@data.supported)
end
# Return an array of the modes advertised by the interface. Returns an
# array of Mode objects. If you know the difference between 'supported'
# and 'advertised', you're one up on me.
def advertised_modes
modes(@data.advertising)
end
# Return a Mode object representing the current detected mode of the
# interface.
def current_mode
speed = @data.speed
speed = :unknown if speed == 65535
duplex = case @data.duplex
when 0 then :half
when 1 then :full
else :unknown
end
port = case @data.port
when 0 then 'T'
when 1 then 'AUI'
when 2 then 'MII'
when 3 then 'F'
when 4 then 'BNC'
when 255 then 'Other'
else 'Unknown'
end
Mode.new(speed, duplex, port)
end
# Return the "best" possible mode supported by this interface.
# This is the highest speed mode with the "best" duplex
# (fec > full > half).
def best_mode
modes = self.advertised_modes
best_speed = modes.map { |m| m.speed }.sort.last
high_speed_modes = modes.find_all { |m| m.speed == best_speed }
# Somewhere recently, RHEL decided to release a kernel or libc update
# that changes the behaviour of the ethtool ioctl so that instead of
# returning EOPNOTSUPP when you ask for available speeds on an interface
# that doesn't support that (like bonded NICs), it now returns success with
# an empty list. WHO DOES THAT SORT OF SHIT?!? So we've got to fake it
# ourselves.
raise Errno::EOPNOTSUPP.new("#{@interface} doesn't support enumerating speed modes") if modes.empty?
if high_speed_modes.length == 0
raise RuntimeError.new("Can't happen: no modes with the best speed?!?")
elsif high_speed_modes.length == 1
high_speed_modes.first
else
duplexes = high_speed_modes.map { |m| m.duplex }
best_duplex = if duplexes.include? :fec
:fec
elsif duplexes.include? :full
:full
else
:half
end
high_speed_modes.find { |m| m.duplex == best_duplex }
end
end
private
PossibleModes = {
1 << 0 => Mode.new(10, :half, 'T'),
1 << 1 => Mode.new(10, :full, 'T'),
1 << 2 => Mode.new(100, :half, 'T'),
1 << 3 => Mode.new(100, :full, 'T'),
1 << 4 => Mode.new(1000, :half, 'T'),
1 << 5 => Mode.new(1000, :full, 'T'),
1 << 12 => Mode.new(10000, :full, 'T'),
1 << 15 => Mode.new(2500, :full, 'X'),
1 << 17 => Mode.new(1000, :full, 'KX'),
1 << 18 => Mode.new(10000, :full, 'KX4'),
1 << 19 => Mode.new(10000, :full, 'KR'),
1 << 20 => Mode.new(10000, :fec, 'R')
}
# Turn a uint32 of bits into a list of supported modes. Sigh.
def modes(data)
PossibleModes.find_all { |m| (m[0] & data) > 0 }.map { |m| m[1] }
end
end
|