File: interface_settings.rb

package info (click to toggle)
ruby-rethtool 0.0.5-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 148 kB
  • sloc: ruby: 167; makefile: 4
file content (138 lines) | stat: -rw-r--r-- 4,154 bytes parent folder | download | duplicates (2)
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