diff --git a/lib/linux_admin/network_interface.rb b/lib/linux_admin/network_interface.rb index ece1d5a..1159478 100644 --- a/lib/linux_admin/network_interface.rb +++ b/lib/linux_admin/network_interface.rb @@ -1,7 +1,8 @@ -require 'ipaddr' - module LinuxAdmin class NetworkInterface + require "ipaddr" + require "json" + # Cached class instance variable for what distro we are running on @dist_class = nil @@ -60,14 +61,16 @@ def reload return false end - parse_ip4(ip_output) - parse_ip6(ip_output, :global) - parse_ip6(ip_output, :link) + addr_info = ip_output["addr_info"] + + parse_ip4(addr_info) + parse_ip6(addr_info, "global") + parse_ip6(addr_info, "link") - @network_conf[:mac] = parse_ip_output(ip_output, %r{link/ether}, 1) + @network_conf[:mac] = ip_output["address"] [4, 6].each do |version| - @network_conf["gateway#{version}".to_sym] = parse_ip_output(ip_route(version), /^default/, 2) + @network_conf["gateway#{version}".to_sym] = ip_route(version, "default")&.dig("gateway") end true end @@ -168,23 +171,13 @@ def stop private - # Parses the output of `ip addr show` - # - # @param output [String] The command output - # @param regex [Regexp] Regular expression to match the desired output line - # @param col [Fixnum] The whitespace delimited column to be returned - # @return [String] The parsed data - def parse_ip_output(output, regex, col) - the_line = output.split("\n").detect { |l| l =~ regex } - the_line.nil? ? nil : the_line.strip.split(' ')[col] - end - # Runs the command `ip addr show ` # # @return [String] The command output # @raise [NetworkInterfaceError] if the command fails def ip_show - Common.run!(Common.cmd("ip"), :params => ["addr", "show", @interface]).output + output = Common.run!(Common.cmd("ip"), :params => ["--json", "addr", "show", @interface]).output + JSON.parse(output).first rescue AwesomeSpawn::CommandResultError => e raise NetworkInterfaceError.new(e.message, e.result) end @@ -194,8 +187,9 @@ def ip_show # @param version [Fixnum] Version of IP protocol (4 or 6) # @return [String] The command output # @raise [NetworkInterfaceError] if the command fails - def ip_route(version) - Common.run!(Common.cmd("ip"), :params => ["-#{version}", 'route']).output + def ip_route(version, route = "default") + output = Common.run!(Common.cmd("ip"), :params => ["--json", "-#{version}", "route", "show", route]).output + JSON.parse(output).first rescue AwesomeSpawn::CommandResultError => e raise NetworkInterfaceError.new(e.message, e.result) end @@ -203,26 +197,24 @@ def ip_route(version) # Parses the IPv4 information from the output of `ip addr show ` # # @param ip_output [String] The command output - def parse_ip4(ip_output) - cidr_ip = parse_ip_output(ip_output, /inet /, 1) - return unless cidr_ip + def parse_ip4(addr_info) + inet = addr_info.detect { |addr| addr["family"] == "inet" } + return if inet.nil? - parts = cidr_ip.split('/') - @network_conf[:address] = parts[0] - @network_conf[:prefix] = parts[1].to_i + @network_conf[:address] = inet["local"] + @network_conf[:prefix] = inet["prefixlen"] end # Parses the IPv6 information from the output of `ip addr show ` # # @param ip_output [String] The command output # @param scope [Symbol] The IPv6 scope (either `:global` or `:local`) - def parse_ip6(ip_output, scope) - cidr_ip = parse_ip_output(ip_output, /inet6 .* scope #{scope}/, 1) - return unless cidr_ip + def parse_ip6(addr_info, scope) + inet6 = addr_info.detect { |addr| addr["family"] == "inet6" && addr["scope"] == scope } + return if inet6.nil? - parts = cidr_ip.split('/') - @network_conf["address6_#{scope}".to_sym] = parts[0] - @network_conf["prefix6_#{scope}".to_sym] = parts[1].to_i + @network_conf["address6_#{scope}".to_sym] = inet6["local"] + @network_conf["prefix6_#{scope}".to_sym] = inet6["prefixlen"] end end end diff --git a/spec/network_interface_spec.rb b/spec/network_interface_spec.rb index 6a50af3..075d450 100644 --- a/spec/network_interface_spec.rb +++ b/spec/network_interface_spec.rb @@ -56,10 +56,10 @@ context "on all systems" do let(:ip_link_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json link]}] } - let(:ip_show_eth0_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[addr show eth0]}] } - let(:ip_show_lo_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[addr show lo]}] } - let(:ip_route_args) { [LinuxAdmin::Common.cmd("ip"), {:params => ['-4', 'route']}] } - let(:ip6_route_args) { [LinuxAdmin::Common.cmd("ip"), {:params => ['-6', 'route']}] } + let(:ip_show_eth0_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json addr show eth0]}] } + let(:ip_show_lo_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json addr show lo]}] } + let(:ip_route_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json -4 route show default]}] } + let(:ip6_route_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json -6 route show default]}] } let(:ifup_args) { [LinuxAdmin::Common.cmd("ifup"), {:params => ["eth0"]}] } let(:ifdown_args) { [LinuxAdmin::Common.cmd("ifdown"), {:params => ["eth0"]}] } let(:ip_link_out) do @@ -69,53 +69,32 @@ end let(:ip_addr_eth0_out) do <<~IP_OUT - 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 - link/ether 00:0c:29:ed:0e:8b brd ff:ff:ff:ff:ff:ff - inet 192.168.1.9/24 brd 192.168.1.255 scope global dynamic eth0 - valid_lft 1297sec preferred_lft 1297sec - inet6 fe80::20c:29ff:feed:e8b/64 scope link - valid_lft forever preferred_lft forever - inet6 fd12:3456:789a:1::1/96 scope global - valid_lft forever preferred_lft forever + [{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"fq_codel","operstate":"UP","group":"default","txqlen":1000,"link_type":"ether","address":"00:0c:29:ed:0e:8b","broadcast":"ff:ff:ff:ff:ff:ff","altnames":["enp0s2","ens2"],"addr_info":[{"family":"inet","local":"192.168.1.9","prefixlen":24,"broadcast":"192.168.255","scope":"global","noprefixroute":true,"label":"eth0","valid_life_time":4294967295,"preferred_life_time":4294967295},{"family":"inet6","local":"fe80::20c:29ff:feed:e8b","prefixlen":64,"scope":"link","noprefixroute":true,"valid_life_time":"forever","preferred_life_time":"forever"},{"family":"inet6","local":"fd12:3456:789a:1::1","prefixlen":96,"scope":"global","noprefixroute":true,"valid_life_time":"forever","preferred_life_time":"forever"}]}] IP_OUT end let(:ip_addr_lo_out) do <<~IP_OUT - 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 - link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 - inet 127.0.0.1/8 scope host lo - valid_lft forever preferred_lft forever - inet6 ::1/128 scope host - valid_lft forever preferred_lft forever + [{"ifindex":1,"ifname":"lo","flags":["LOOPBACK","UP","LOWER_UP"],"mtu":65536,"qdisc":"noqueue","operstate":"UNKNOWN","group":"default","txqlen":1000,"link_type":"loopback","address":"00:00:00:00:00:00","broadcast":"00:00:00:00:00:00","addr_info":[{"family":"inet","local":"127.0.0.1","prefixlen":8,"scope":"host","label":"lo","valid_life_time":4294967295,"preferred_life_time":4294967295},{"family":"inet6","local":"::1","prefixlen":128,"scope":"host","valid_life_time":"forever","preferred_life_time":"forever"}]}] IP_OUT end let(:ip6_addr_out) do <<~IP_OUT - 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 - link/ether 00:0c:29:ed:0e:8b brd ff:ff:ff:ff:ff:ff - inet6 fe80::20c:29ff:feed:e8b/64 scope link - valid_lft forever preferred_lft forever - inet6 fd12:3456:789a:1::1/96 scope global - valid_lft forever preferred_lft forever + [{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"fq_codel","operstate":"UP","group":"default","txqlen":1000,"link_type":"ether","address":"00:0c:29:ed:0e:8b","broadcast":"ff:ff:ff:ff:ff:ff","altnames":["enp0s2","ens2"],"addr_info":[{"family":"inet6","local":"fe80::20c:29ff:feed:e8b","prefixlen":64,"scope":"link","noprefixroute":true,"valid_life_time":"forever","preferred_life_time":"forever"},{"family":"inet6","local":"fd12:3456:789a:1::1","prefixlen":96,"scope":"global","noprefixroute":true,"valid_life_time":"forever","preferred_life_time":"forever"}]}] IP_OUT end let(:ip_route_out) do <<~IP_OUT - default via 192.168.1.1 dev eth0 proto static metric 100 - 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.9 metric 100 + [{"dst":"default","gateway":"192.168.1.1","dev":"eth0","protocol":"static","metric":100,"flags":[]}] IP_OUT end let(:ip6_route_out) do <<~IP_OUT - default via d:e:a:d:b:e:e:f dev eth0 proto static metric 100 - fc00:dead:beef:a::/64 dev virbr1 proto kernel metric 256 linkdown pref medium - fe80::/64 dev eth0 proto kernel scope link metric 100 + [{"dst":"default","gateway":"d:e:a:d:b:e:e:f","dev":"eth0","protocol":"static","metric":100,"flags":[]}] IP_OUT end let(:ip_none_addr_out) do <<~IP_OUT - 2: eth0: mtu 1500 qdisc fq_codel master virbr1 state DOWN group default qlen 1000 - link/ether 52:54:00:ce:b4:f4 brd ff:ff:ff:ff:ff:ff + [{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"fq_codel","operstate":"UP","group":"default","txqlen":1000,"link_type":"ether","address":"00:0c:29:ed:0e:8b","broadcast":"ff:ff:ff:ff:ff:ff","altnames":["enp0s2","ens2"],"addr_info":[]}] IP_OUT end