Class: Aws::ENI::Interface
- Inherits:
-
Object
- Object
- Aws::ENI::Interface
- Extended by:
- Enumerable
- Defined in:
- lib/aws-eni/interface.rb
Class Attribute Summary collapse
-
.verbose ⇒ Object
Returns the value of attribute verbose.
Instance Attribute Summary collapse
-
#device_number ⇒ Object
readonly
Returns the value of attribute device_number.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#route_table ⇒ Object
readonly
Returns the value of attribute route_table.
Class Method Summary collapse
-
.[](index) ⇒ Object
Array-like accessor to automatically instantiate our class.
-
.clean ⇒ Object
Purge and deconfigure non-existent interfaces from the cache.
-
.cmd(command, options = {}) ⇒ Object
Execute an ‘ip’ command.
-
.configure(selector = nil, options = {}) ⇒ Object
Configure all available interfaces identified by an optional selector.
-
.deconfigure(selector = nil) ⇒ Object
Remove configuration on available interfaces identified by an optional selector.
-
.each(&block) ⇒ Object
Iterate over available ethernet interfaces (required for Enumerable).
-
.enabled ⇒ Object
Return array of enabled interfaces.
-
.exec(command, options = {}) ⇒ Object
Execute a command, returns output as string or nil on error.
-
.filter(filter = nil) ⇒ Object
Return an array of available interfaces identified by name, id, hwaddr, or subnet id.
-
.mutable? ⇒ Boolean
Test whether we have permission to run RTNETLINK commands.
-
.next_available_index ⇒ Object
Return the next unused device index.
-
.test(ip, options = {}) ⇒ Object
Test connectivity from a given ip address.
Instance Method Summary collapse
-
#add_alias(ip) ⇒ Object
Add a secondary ip to this interface.
-
#assert(attr) ⇒ Object
Throw exception unless this interface matches the provided attributes else returns self.
-
#configure(dry_run = false) ⇒ Object
Initialize a new interface config.
-
#deconfigure ⇒ Object
Remove configuration for an interface.
-
#disable ⇒ Object
Disable our interface.
-
#enable ⇒ Object
Enable our interface and create necessary routes.
-
#enabled? ⇒ Boolean
Check whether our interface is enabled.
-
#exists? ⇒ Boolean
Verify device exists on our system.
- #gateway ⇒ Object
-
#has_ip?(ip_addr) ⇒ Boolean
Return true if the ip address is associated with this interface.
-
#hwaddr ⇒ Object
Get our interface’s MAC address.
-
#info ⇒ Object
Validate and return basic interface metadata.
-
#initialize(name, auto_config = true) ⇒ Interface
constructor
A new instance of Interface.
- #interface_id ⇒ Object
-
#local_ips ⇒ Object
Return an array of configured ip addresses (primary + secondary).
-
#meta_ips ⇒ Object
Return an array of ip addresses found in our instance metadata.
- #prefix ⇒ Object
-
#public_ips ⇒ Object
Return a hash of local/public ip associations found in instance metadata.
-
#remove_alias(ip) ⇒ Object
Remove a secondary ip from this interface.
- #subnet_cidr ⇒ Object
- #subnet_id ⇒ Object
-
#to_h ⇒ Object
Return an array representation of our interface config, including public ip associations and enabled status.
Constructor Details
#initialize(name, auto_config = true) ⇒ Interface
Returns a new instance of Interface.
147 148 149 150 151 152 153 154 155 156 |
# File 'lib/aws-eni/interface.rb', line 147 def initialize(name, auto_config = true) unless name =~ /^eth([0-9]+)$/ raise Errors::InvalidInterface, "Invalid interface: #{name}" end @name = name @device_number = $1.to_i @route_table = @device_number + 10000 @lock = Mutex.new configure if auto_config end |
Class Attribute Details
.verbose ⇒ Object
Returns the value of attribute verbose.
15 16 17 |
# File 'lib/aws-eni/interface.rb', line 15 def verbose @verbose end |
Instance Attribute Details
#device_number ⇒ Object (readonly)
Returns the value of attribute device_number.
145 146 147 |
# File 'lib/aws-eni/interface.rb', line 145 def device_number @device_number end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
145 146 147 |
# File 'lib/aws-eni/interface.rb', line 145 def name @name end |
#route_table ⇒ Object (readonly)
Returns the value of attribute route_table.
145 146 147 |
# File 'lib/aws-eni/interface.rb', line 145 def route_table @route_table end |
Class Method Details
.[](index) ⇒ Object
Array-like accessor to automatically instantiate our class
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/aws-eni/interface.rb', line 18 def [](index) case index when Integer @lock.synchronize do @instance_cache ||= [] @instance_cache[index] ||= new("eth#{index}", false) end when nil self[next_available_index] when /^(?:eth)?([0-9]+)$/ self[$1.to_i] when /^eni-/ find { |dev| dev.interface_id == index } when /^[0-9a-f:]+$/i find { |dev| dev.hwaddr.casecmp(index) == 0 } when /^[0-9\.]+$/ find { |dev| dev.has_ip?(index) } end.tap do |dev| raise Errors::UnknownInterface, "No interface found matching #{index}" unless dev end end |
.clean ⇒ Object
Purge and deconfigure non-existent interfaces from the cache
41 42 43 44 |
# File 'lib/aws-eni/interface.rb', line 41 def clean # exists? will automatically call deconfigure if necessary @instance_cache.map!{ |dev| dev if dev.exists? } end |
.cmd(command, options = {}) ⇒ Object
Execute an ‘ip’ command
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/aws-eni/interface.rb', line 101 def cmd(command, = {}) [:sudo] = [:sudo] != false errors = [:errors] [:errors] = true begin exec("/sbin/ip #{command}", ) rescue Errors::InterfaceOperationError => e case e. when /operation not permitted/i, /password is required/i raise Errors::InterfacePermissionError, "Operation not permitted" else raise if errors end end end |
.configure(selector = nil, options = {}) ⇒ Object
Configure all available interfaces identified by an optional selector
64 65 66 67 68 |
# File 'lib/aws-eni/interface.rb', line 64 def configure(selector = nil, = {}) filter(selector).reduce(0) do |count, dev| count + dev.configure([:dry_run]) end end |
.deconfigure(selector = nil) ⇒ Object
Remove configuration on available interfaces identified by an optional selector
72 73 74 75 |
# File 'lib/aws-eni/interface.rb', line 72 def deconfigure(selector = nil) filter(selector).each(&:deconfigure) true end |
.each(&block) ⇒ Object
Iterate over available ethernet interfaces (required for Enumerable)
54 55 56 |
# File 'lib/aws-eni/interface.rb', line 54 def each(&block) Dir.entries("/sys/class/net/").grep(/^eth[0-9]+$/){ |name| self[name] }.each(&block) end |
.enabled ⇒ Object
Return array of enabled interfaces
59 60 61 |
# File 'lib/aws-eni/interface.rb', line 59 def enabled select(&:enabled?) end |
.exec(command, options = {}) ⇒ Object
Execute a command, returns output as string or nil on error
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/aws-eni/interface.rb', line 125 def exec(command, = {}) output = nil errors = [:errors] verbose = self.verbose || [:verbose] command = "sudo -n #{command}" if [:sudo] puts command if verbose Open3.popen3(command) do |i,o,e,t| if t.value.success? output = o.read else error = e.read warn "Warning: #{error}" if verbose raise Errors::InterfaceOperationError, error if errors end end output end |
.filter(filter = nil) ⇒ Object
Return an array of available interfaces identified by name, id, hwaddr, or subnet id.
79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/aws-eni/interface.rb', line 79 def filter(filter = nil) case filter when nil to_a when /^eni-/, /^eth[0-9]+$/, /^[0-9a-f:]+$/i, /^[0-9\.]+$/ [*self[filter]] when /^subnet-/ select { |dev| dev.subnet_id == filter } end.tap do |devs| raise Errors::UnknownInterface, "No interface found matching #{filter}" if devs.nil? || devs.empty? end end |
.mutable? ⇒ Boolean
Test whether we have permission to run RTNETLINK commands
93 94 95 96 97 98 |
# File 'lib/aws-eni/interface.rb', line 93 def mutable? cmd('link set dev eth0') # innocuous command true rescue Errors::InterfacePermissionError false end |
.next_available_index ⇒ Object
Return the next unused device index
47 48 49 50 51 |
# File 'lib/aws-eni/interface.rb', line 47 def next_available_index for index in 0..32 do break index unless self[index].exists? end end |
.test(ip, options = {}) ⇒ Object
Test connectivity from a given ip address
118 119 120 121 122 |
# File 'lib/aws-eni/interface.rb', line 118 def test(ip, = {}) timeout = Integer([:timeout] || 30) target = [:target] || '8.8.8.8' !!exec("ping -w #{timeout} -c 1 -I #{ip} #{target}") end |
Instance Method Details
#add_alias(ip) ⇒ Object
Add a secondary ip to this interface
332 333 334 335 336 337 |
# File 'lib/aws-eni/interface.rb', line 332 def add_alias(ip) cmd("addr add #{ip}/#{prefix} brd + dev #{name}") unless name == 'eth0' || cmd("rule list").include?("from #{ip} lookup #{route_table}") cmd("rule add from #{ip} lookup #{route_table}") end end |
#assert(attr) ⇒ Object
Throw exception unless this interface matches the provided attributes else returns self
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'lib/aws-eni/interface.rb', line 360 def assert(attr) error = nil attr.find do |attr,val| next if val.nil? error = case attr when :exists if val "The specified interface does not exist." unless exists? else "Interface #{name} exists." if exists? end when :enabled if val "Interface #{name} is not enabled." unless enabled? else "Interface #{name} is not disabled." if enabled? end when :name, :device_name "The specified interface does not match" unless name == val when :index, :device_index, :device_number "Interface #{name} is device number #{val}" unless device_number == val.to_i when :hwaddr "Interface #{name} does not match hwaddr #{val}" unless hwaddr == val when :interface_id "Interface #{name} does not have interface id #{val}" unless interface_id == val when :subnet_id "Interface #{name} does not have subnet id #{val}" unless subnet_id == val when :ip, :has_ip "Interface #{name} does not have IP #{val}" unless has_ip? val when :public_ip "Interface #{name} does not have public IP #{val}" unless public_ips.has_value? val when :local_ip, :private_ip "Interface #{name} does not have private IP #{val}" unless local_ips.include? val else "Unknown attribute: #{attr}" end end raise Errors::UnknownInterface, error if error self end |
#configure(dry_run = false) ⇒ Object
Initialize a new interface config
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/aws-eni/interface.rb', line 268 def configure(dry_run = false) changes = 0 prefix = self.prefix # prevent exists? check on each use local_primary, *local_aliases = local_ips , * = # ensure primary ip address is correct if name != 'eth0' && local_primary != unless dry_run deconfigure cmd("addr add #{}/#{prefix} brd + dev #{name}") end changes += 1 end # add missing secondary ips ( - local_aliases).each do |ip| cmd("addr add #{ip}/#{prefix} brd + dev #{name}") unless dry_run changes += 1 end # remove extra secondary ips (local_aliases - ).each do |ip| cmd("addr del #{ip}/#{prefix} dev #{name}") unless dry_run changes += 1 end # add and remove source-ip based rules unless name == 'eth0' rules_to_add = || [] cmd("rule list").lines.grep(/^([0-9]+):.*\s([0-9\.]+)\s+lookup #{route_table}/) do unless rules_to_add.delete($2) cmd("rule delete pref #{$1}") unless dry_run changes += 1 end end rules_to_add.each do |ip| cmd("rule add from #{ip} lookup #{route_table}") unless dry_run changes += 1 end end @clean = nil changes end |
#deconfigure ⇒ Object
Remove configuration for an interface
316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/aws-eni/interface.rb', line 316 def deconfigure # assume eth0 primary ip is managed by dhcp if name == 'eth0' cmd("addr flush dev eth0 secondary") else cmd("rule list").lines.grep(/^([0-9]+):.*lookup #{route_table}/) do cmd("rule delete pref #{$1}") end cmd("addr flush dev #{name}") cmd("route flush table #{route_table}") cmd("route flush cache") end @clean = true end |
#disable ⇒ Object
Disable our interface
258 259 260 |
# File 'lib/aws-eni/interface.rb', line 258 def disable cmd("link set dev #{name} down") end |
#enable ⇒ Object
Enable our interface and create necessary routes
251 252 253 254 255 |
# File 'lib/aws-eni/interface.rb', line 251 def enable cmd("link set dev #{name} up") cmd("route add default via #{gateway} dev #{name} table #{route_table}") cmd("route flush cache") end |
#enabled? ⇒ Boolean
Check whether our interface is enabled
263 264 265 |
# File 'lib/aws-eni/interface.rb', line 263 def enabled? exists? && cmd("link show up", sudo: false).include?(name) end |
#exists? ⇒ Boolean
Verify device exists on our system
169 170 171 172 173 |
# File 'lib/aws-eni/interface.rb', line 169 def exists? File.directory?("/sys/class/net/#{name}").tap do |exists| deconfigure unless exists || @clean end end |
#gateway ⇒ Object
208 209 210 |
# File 'lib/aws-eni/interface.rb', line 208 def gateway IPAddr.new(subnet_cidr).succ.to_s end |
#has_ip?(ip_addr) ⇒ Boolean
Return true if the ip address is associated with this interface
348 349 350 351 352 353 354 355 356 |
# File 'lib/aws-eni/interface.rb', line 348 def has_ip?(ip_addr) if IPAddr.new(subnet_cidr) === IPAddr.new(ip_addr) # ip within subnet local_ips.include? ip_addr else # ip outside subnet public_ips.has_value? ip_addr end end |
#hwaddr ⇒ Object
Get our interface’s MAC address
159 160 161 162 163 164 165 166 |
# File 'lib/aws-eni/interface.rb', line 159 def hwaddr begin exists? && IO.read("/sys/class/net/#{name}/address").strip rescue Errno::ENOENT end.tap do |address| raise Errors::UnknownInterface, "Interface #{name} not found on this machine" unless address end end |
#info ⇒ Object
Validate and return basic interface metadata
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/aws-eni/interface.rb', line 176 def info @lock.synchronize do hwaddr = self.hwaddr unless @meta_cache && @meta_cache[:hwaddr] == hwaddr @meta_cache = Meta.connection do raise Errors::MetaBadResponse unless Meta.interface(hwaddr, '', not_found: nil) { hwaddr: hwaddr, interface_id: Meta.interface(hwaddr, 'interface-id'), subnet_id: Meta.interface(hwaddr, 'subnet-id'), subnet_cidr: Meta.interface(hwaddr, 'subnet-ipv4-cidr-block') }.freeze end end @meta_cache end rescue Errors::MetaConnectionFailed raise Errors::InvalidInterface, "Interface #{name} could not be found in the EC2 instance meta-data" end |
#interface_id ⇒ Object
196 197 198 |
# File 'lib/aws-eni/interface.rb', line 196 def interface_id info[:interface_id] end |
#local_ips ⇒ Object
Return an array of configured ip addresses (primary + secondary)
217 218 219 220 221 222 223 224 |
# File 'lib/aws-eni/interface.rb', line 217 def local_ips list = cmd("addr show dev #{name} primary", sudo: false, errors: true) + cmd("addr show dev #{name} secondary", sudo: false, errors: true) list.lines.grep(/inet ([0-9\.]+)\/.* #{name}/i){ $1 } rescue raise Errors::UnknownInterface, "Interface #{name} not found on this machine" unless exists? raise end |
#meta_ips ⇒ Object
Return an array of ip addresses found in our instance metadata
227 228 229 230 231 232 |
# File 'lib/aws-eni/interface.rb', line 227 def # hack to use cached hwaddr when available since this is often polled # continuously for changes hwaddr = (@meta_cache && @meta_cache[:hwaddr]) || hwaddr Meta.interface(hwaddr, 'local-ipv4s', cache: false).lines.map(&:strip) end |
#prefix ⇒ Object
212 213 214 |
# File 'lib/aws-eni/interface.rb', line 212 def prefix subnet_cidr.split('/').last.to_i end |
#public_ips ⇒ Object
Return a hash of local/public ip associations found in instance metadata
235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/aws-eni/interface.rb', line 235 def public_ips hwaddr = self.hwaddr Hash[ Meta.connection do Meta.interface(hwaddr, 'ipv4-associations/', not_found: '', cache: false).lines.map do |public_ip| public_ip.strip! unless private_ip = Meta.interface(hwaddr, "ipv4-associations/#{public_ip}", not_found: nil, cache: false) raise Errors::MetaBadResponse end [ private_ip, public_ip ] end end ] end |
#remove_alias(ip) ⇒ Object
Remove a secondary ip from this interface
340 341 342 343 344 345 |
# File 'lib/aws-eni/interface.rb', line 340 def remove_alias(ip) cmd("addr del #{ip}/#{prefix} dev #{name}") unless name == 'eth0' || !cmd("rule list").match(/([0-9]+):\s+from #{ip} lookup #{route_table}/) cmd("rule delete pref #{$1}") end end |
#subnet_cidr ⇒ Object
204 205 206 |
# File 'lib/aws-eni/interface.rb', line 204 def subnet_cidr info[:subnet_cidr] end |
#subnet_id ⇒ Object
200 201 202 |
# File 'lib/aws-eni/interface.rb', line 200 def subnet_id info[:subnet_id] end |
#to_h ⇒ Object
Return an array representation of our interface config, including public ip associations and enabled status
403 404 405 406 407 408 409 410 411 412 |
# File 'lib/aws-eni/interface.rb', line 403 def to_h info.merge( name: name, device_number: device_number, route_table: route_table, local_ips: local_ips, public_ips: public_ips, enabled: enabled? ) end |