From 2cf04c331a063d8365c37ac063eee563c586fd41 Mon Sep 17 00:00:00 2001
From: Philippe PITTOLI
Date: Mon, 14 Oct 2019 00:42:15 +0200
Subject: [PATCH] split in several files
---
src/autodetect_environment.cr | 14 +
src/cli.cr | 170 +++++
src/configuration.cr | 292 ++++++++
src/context.cr | 17 +
src/do.cr | 22 +
src/main.cr | 1001 +--------------------------
src/network_commands.cr | 280 ++++++++
src/network_configuration_parser.cr | 210 ++++++
8 files changed, 1012 insertions(+), 994 deletions(-)
create mode 100644 src/autodetect_environment.cr
create mode 100644 src/cli.cr
create mode 100644 src/configuration.cr
create mode 100644 src/context.cr
create mode 100644 src/do.cr
create mode 100644 src/network_commands.cr
create mode 100644 src/network_configuration_parser.cr
diff --git a/src/autodetect_environment.cr b/src/autodetect_environment.cr
new file mode 100644
index 0000000..014ca85
--- /dev/null
+++ b/src/autodetect_environment.cr
@@ -0,0 +1,14 @@
+
+class Autodetect
+ class_property print_autodetect : Bool = false
+
+ def self.which(cmd : String)
+ if Process.run("which", [ cmd ]).success?
+ puts "#{cmd} installed" if print_autodetect
+ true
+ else
+ puts "#{cmd} not installed" if print_autodetect
+ false
+ end
+ end
+end
diff --git a/src/cli.cr b/src/cli.cr
new file mode 100644
index 0000000..d4d07b5
--- /dev/null
+++ b/src/cli.cr
@@ -0,0 +1,170 @@
+
+file_option : String? = nil
+
+OptionParser.parse! do |parser|
+ parser.on "-s", "--simulation", "Export the network configuration." do
+ Context.simulation = true
+ end
+
+ parser.on "-a", "--print-autodetect", "Print autodetection of the installed programs." do
+ Context.print_autodetect = true
+ end
+
+ parser.on "-w wireless-configuration-program", "--wireless wireless-configuration-program", "iw" do |prog|
+ Context.prefered_wireless_configuration_program = prog
+ end
+
+ parser.on "-n network-configuration-program", "--net-conf network-configuration-program", "ifconfig | ip" do |prog|
+ Context.prefered_network_configuration_program = prog
+ end
+
+ parser.on "-d dhcp-client-program", "--dhcp-client dhcp-client-program", "udhcpc | dhclient" do |prog|
+ Context.prefered_dhcp_client = prog
+ end
+
+ parser.on "-r root", "--root root", "Root where to search for /etc/hostname.* files." do |optsn|
+ Context.root = optsn
+ end
+
+ parser.on "-f file", "--file file", "Parse a configuration file." do |optsn|
+ file_option = optsn
+ end
+
+ # 0: nothing is printed, 1: only events, 2: events and messages
+ parser.on "-v verbosity", "--verbosity verbosity", "Verbosity (0-2). Default: 1" do |optsn|
+ Context.verbosity = optsn.to_i
+ end
+
+ parser.missing_option do |opt|
+ STDERR.puts "You missed the argument for option #{opt}"
+ # TODO: explain the different arguments
+ exit 1
+ end
+
+ parser.invalid_option do |flag|
+ STDERR.puts "Error: #{flag} not a valid option"
+ exit 1
+ end
+
+ parser.unknown_args do |arg|
+ Context.command = arg.shift
+ Context.args = arg
+
+ case Context.command
+ when /^(list)/
+ when /^(up)/
+ when /^(down)/
+ when /^(scan)/
+ else
+ STDERR.puts "Command #{Context.command} not understood"
+ exit 1
+ end
+
+ # unless arg.empty?
+ # STDERR.puts "unknown arg: #{arg}"
+ # exit 1
+ # end
+ end
+
+ parser.on "-h", "--help", "Show this help" do
+ puts parser
+ exit 0
+ end
+end
+
+
+Do.simulation = Context.simulation
+Autodetect.print_autodetect = Context.print_autodetect
+
+#
+# discover available configuration commands
+#
+
+# ifconfig = *bsd and some linux
+# ip = linux
+possible_network_configuration_cmds = {
+ "ifconfig" => NetworkCommands::IfconfigCommand,
+ "ip" => NetworkCommands::IPCommand
+}
+
+# udhcpc = busybox dhcp client
+possible_dhcp_clients = {
+ "udhcpc" => NetworkCommands::UDHCPCCommand,
+ "dhclient" => NetworkCommands::DHClientCommand
+}
+
+# iw = linux
+possible_wireless_configuration_cmds = {
+ "iw" => NetworkCommands::IWCommand,
+ "ifconfig" => NetworkCommands::IfconfigCommand
+}
+
+key = Context.prefered_network_configuration_program
+key = possible_network_configuration_cmds.keys.find { |key| Autodetect.which(key) } if key.nil?
+# should crash if there is no network command installed
+NetworkCommands.cmd_network_configuration = possible_network_configuration_cmds[key.not_nil!]
+
+key = Context.prefered_dhcp_client
+key = possible_dhcp_clients.keys.find { |key| Autodetect.which(key) } if key.nil?
+# should not crash if there is no
+NetworkCommands.cmd_dhcp_client = possible_dhcp_clients[key] unless key.nil?
+
+key = Context.prefered_wireless_configuration_program
+key = possible_wireless_configuration_cmds.keys.find { |key| Autodetect.which(key) } if key.nil?
+# should crash if there is no wireless command installed
+NetworkCommands.cmd_wireless_configuration = possible_wireless_configuration_cmds[key.not_nil!]
+
+
+
+files = Array(String).new
+Dir.children("#{Context.root}/etc/").each do |f|
+ if /^hostname\./.match(f)
+ files << f
+ end
+end
+
+interface_files = Array(String).new
+
+if ! file_option.nil?
+ # file passed via the '-f' option
+ # TODO: why having to force "not_nil!" ? Seems like a compiler bug
+ interface_files << file_option.not_nil!
+elsif Context.args.empty?
+ # every configured interface
+ files.each do |f|
+ interface_files << "#{Context.root}/etc/#{f}"
+ end
+else
+ # only interfaces in arguments
+ Context.args.each do |interface|
+ interface_files << "#{Context.root}/etc/hostname.#{interface}"
+ end
+end
+
+
+
+begin
+ case Context.command
+ when "list"
+ interface_files.each do |f|
+ puts NetworkConfigurationParser.parse_file(f)
+ end
+ when "up"
+ interface_files.each do |f|
+ network_configuration = NetworkConfigurationParser.parse_file(f)
+ network_configuration.execute
+ end
+ when "down"
+ interface_files.each do |f|
+ network_configuration = NetworkConfigurationParser.parse_file(f)
+ network_configuration.down
+ end
+ when "scan"
+ interface_files.each do |f|
+ network_configuration = NetworkConfigurationParser.parse_file(f)
+ network_configuration.scan
+ end
+ end
+rescue e
+ STDERR.puts "#{CRED}Exception: #{CRESET}#{e}"
+end
diff --git a/src/configuration.cr b/src/configuration.cr
new file mode 100644
index 0000000..ebafe4c
--- /dev/null
+++ b/src/configuration.cr
@@ -0,0 +1,292 @@
+
+class NotSetup
+ def to_s(io : IO)
+ io << "not setup"
+ end
+end
+
+class Autoconfiguration
+ def to_s(io : IO)
+ io << "autoconfiguration"
+ end
+end
+
+class DHCP
+ def to_s(io : IO)
+ io << "dhcp"
+ end
+end
+
+
+class WirelessAPSetup
+ property ssid : String
+
+ # This is a list of parameters that should be unique to each AP
+ property up : Bool
+ property description : String?
+ property mtu : Int32?
+ property main_ip_v4 : IPAddress | DHCP | NotSetup
+ property main_ip_v6 : IPAddress | DHCP | Autoconfiguration | NotSetup
+ property aliasses_v4 : Array(IPAddress)
+ property aliasses_v6 : Array(IPAddress)
+ property dns : Array(IPAddress)
+
+ # we currently only support WPA2-PSK wireless security mechanism
+ property security : WPA
+
+ class WPA
+ property key : String
+ def initialize(@key)
+ end
+ end
+
+ def initialize(@ssid, @security)
+ @main_ip_v4 = NotSetup.new
+ @main_ip_v6 = NotSetup.new
+ @aliasses_v4 = Array(IPAddress).new
+ @aliasses_v6 = Array(IPAddress).new
+ @up = true
+ @dns = Array(IPAddress).new
+ end
+
+
+ def to_s(io : IO)
+ io << to_string
+ end
+
+ def to_string
+ String.build do |str|
+ str << "\t#{CBLUE}#{ssid}#{CRESET}\n"
+
+ str << "\t\t#description #{description.not_nil!}\n" unless description.nil?
+ str << "\t\t#{@up? "up" : "down"}\n"
+ str << "\t\tmtu #{mtu}\n" unless mtu.nil?
+
+ # ipv4
+ unless main_ip_v4.is_a?(NotSetup)
+ str << "\t\tinet #{main_ip_v4}\n"
+
+ aliasses_v4.each do |a|
+ str << "\t\talias #{a}\n"
+ end
+ end
+
+ # ipv6
+ unless main_ip_v6.is_a?(NotSetup)
+ str << "\t\tinet6 #{main_ip_v6}\n"
+
+ @aliasses_v6.each do |a|
+ str << "\t\talias6 #{a}\n"
+ end
+ end
+
+ dns.each do |ip|
+ str << "\t\tdns: #{ip}\n"
+ end
+
+ # to improve readability
+ str << "\n"
+ end
+ end
+end
+
+
+#
+# interface configuration
+#
+
+class InterfaceConfiguration
+
+ property name : String
+ property up : Bool
+ property description : String?
+ property mtu : Int32?
+ property wireless : Bool
+ property main_ip_v4 : IPAddress | DHCP | NotSetup
+ property main_ip_v6 : IPAddress | Autoconfiguration | DHCP | NotSetup
+ property aliasses_v4 : Array(IPAddress)
+ property aliasses_v6 : Array(IPAddress)
+ property wireless_networks : Hash(String, WirelessAPSetup)
+ property dns : Array(IPAddress)
+
+ def initialize (@name, @up,
+ @description,
+ @mtu,
+ @main_ip_v4, @main_ip_v6, aliasses,
+ @wireless, @wireless_networks,
+ @dns = Array(IPAddress).new
+ )
+
+ @aliasses_v4 = Array(IPAddress).new
+ @aliasses_v6 = Array(IPAddress).new
+
+ aliasses.each do |ip|
+ if ip.ipv4?
+ aliasses_v4 << ip
+ else
+ aliasses_v6 << ip
+ end
+ end
+ end
+
+ def to_s(io : IO)
+ io << to_string
+ end
+
+ def to_string
+ String.build do |str|
+ if NetworkCommands.interface_exists(@name)
+ str << "#{CGREEN}#{@name}#{CRESET}\n"
+ else
+ str << "#{CRED}#{@name}#{CRESET}\n"
+ end
+
+ str << "\tdescription: '#{description.not_nil!}'\n" unless description.nil?
+
+ str << "\t#{@up? "up" : "down"}\n"
+
+ unless mtu.nil?
+ str << "\tmtu #{mtu}\n"
+ end
+
+ # ipv4
+ unless main_ip_v4.is_a?(NotSetup)
+ str << "\tinet #{main_ip_v4}\n"
+
+ aliasses_v4.each do |a|
+ str << "\talias #{a}\n"
+ end
+ end
+
+ # warning: alias but no main ip address
+ if main_ip_v4.is_a?(NotSetup) && ! aliasses_v4.empty?
+ str << "\t#{CRED}alias configured but no main ipv4 configuration.#{CRESET}\n"
+ str << "\t#{CRED}Should main ipv4 be obtained from DHCP? Static configuration?#{CRESET}\n"
+ end
+
+ # ipv6
+ unless main_ip_v6.is_a?(NotSetup)
+ str << "\tinet6 #{main_ip_v6}\n"
+
+ aliasses_v6.each do |a|
+ str << "\talias6 #{a}\n"
+ end
+ end
+
+ # warning: alias but no main ip address
+ if main_ip_v6.is_a?(NotSetup) && ! aliasses_v6.empty?
+ str << "\t#{CRED}alias6 configured but no main ipv6 configuration.#{CRESET}\n"
+ str << "\t#{CRED}Should main ipv6 be obtained from autoconfiguration? DHCP? Static configuration?#{CRESET}\n"
+ end
+
+ unless dns.empty?
+ dns.each do |ip|
+ str << "\tdns: #{ip}\n"
+ end
+ end
+
+ unless wireless_networks.empty?
+ # to improve readability
+ str << "\n"
+ wireless_networks.each do |k,v|
+ str << v
+ end
+ end
+ end
+ end
+
+ # configure the interface
+ def execute
+ unless NetworkCommands.interface_exists(@name)
+ raise "The interface #{@name} doesn't exists, yet."
+ end
+
+ # TODO: treat differently wireless and non-wireless interfaces
+ if @wireless
+ puts "interface #{name} is wireless"
+ puts "TODO:"
+ puts "1. scan for SSID"
+ puts "2. select configured SSID then try to connect"
+ puts "3. connectivity check with the gateway"
+ else
+ puts "interface #{name} is not wireless"
+ end
+
+ if up
+ NetworkCommands.up name
+ else
+ puts "not marked as 'up' -- ending here"
+ return
+ end
+
+ unless mtu.nil?
+ NetworkCommands.mtu name, mtu
+ end
+
+ unless description.nil?
+ NetworkCommands.description name, description.not_nil!
+ end
+
+ # ipv4 configuration
+ main_ip_v4.tap do |ip|
+ case ip
+ when IPAddress
+ NetworkCommands.set_ip name, ip
+ when DHCP
+ NetworkCommands.dhcp name
+ when NotSetup
+ # do nothing
+ else
+ raise "ipv4 configuration: neither static nor dynamic"
+ end
+
+ # We wont setup aliasses unless there is an actual IP address
+ if ip != NotSetup
+ aliasses_v4.each do |ip_alias|
+ NetworkCommands.set_alias name, ip_alias
+ end
+ end
+ end
+
+ # ipv6 configuration
+ main_ip_v6.tap do |ip|
+ case ip
+ when IPAddress
+ NetworkCommands.set_ip name, ip
+ when Autoconfiguration
+ NetworkCommands.autoconfiguration name
+ when DHCP
+ NetworkCommands.dhcp6 name
+ when NotSetup
+ # do nothing
+ else
+ raise "ipv6 configuration: neither static nor dynamic"
+ end
+
+ # We wont setup aliasses unless there is an actual IP address
+ if ip != NotSetup
+ aliasses_v6.each do |ip_alias|
+ NetworkCommands.set_alias name, ip_alias
+ end
+ end
+ end
+ end
+
+ def down
+ unless NetworkCommands.interface_exists(@name)
+ raise "The interface #{@name} doesn't exists or is already down."
+ end
+
+ NetworkCommands.flush name
+ NetworkCommands.down name
+ end
+
+ def scan
+ unless NetworkCommands.interface_exists(@name)
+ raise "The interface #{@name} doesn't exists or is already down."
+ end
+
+ ssid_list = NetworkCommands.scan name
+ pp! ssid_list
+ end
+end
diff --git a/src/context.cr b/src/context.cr
new file mode 100644
index 0000000..3dfb2de
--- /dev/null
+++ b/src/context.cr
@@ -0,0 +1,17 @@
+
+class Context
+ class_property root : String = "/"
+
+ class_property simulation = false
+ class_property verbosity = 1
+
+ class_property prefered_network_configuration_program : String? = nil
+ class_property prefered_wireless_configuration_program : String? = nil
+ class_property prefered_dhcp_client : String? = nil
+
+ class_property root = "/"
+ class_property print_autodetect = false
+
+ class_property command = "list"
+ class_property args = Array(String).new
+end
diff --git a/src/do.cr b/src/do.cr
new file mode 100644
index 0000000..6a2ed9c
--- /dev/null
+++ b/src/do.cr
@@ -0,0 +1,22 @@
+
+class Do < Process
+ class_property simulation = false
+
+ def self.run(cmd : String, params : Array(String) = nil)
+ if @@simulation
+ puts "simulation, do: #{cmd} #{params.join(" ")}"
+ Process::Status.new 0
+ else
+ Process.run cmd, params
+ end
+ end
+
+ def self.run(cmd : String, params : Array(String) = nil, &block : Process -> _)
+ if @@simulation
+ puts "simulation, do: #{cmd} #{params.join(" ")}"
+ Process::Status.new 0
+ else
+ Process.run cmd, params, &block
+ end
+ end
+end
diff --git a/src/main.cr b/src/main.cr
index 66741cd..5ba3392 100644
--- a/src/main.cr
+++ b/src/main.cr
@@ -2,997 +2,10 @@
require "option_parser"
require "ipaddress"
require "./colors"
-
-class Context
- class_property root : String = "/"
-
- class_property simulation = false
- class_property verbosity = 1
-
- class_property prefered_network_configuration_program : String? = nil
- class_property prefered_wireless_configuration_program : String? = nil
- class_property prefered_dhcp_client : String? = nil
-
- class_property root = "/"
- class_property print_autodetect = false
-
- class_property command = "list"
- class_property args = Array(String).new
-end
-
-file_option : String? = nil
-
-OptionParser.parse! do |parser|
- parser.on "-s", "--simulation", "Export the network configuration." do
- Context.simulation = true
- end
-
- parser.on "-a", "--print-autodetect", "Print autodetection of the installed programs." do
- Context.print_autodetect = true
- end
-
- parser.on "-w wireless-configuration-program", "--wireless wireless-configuration-program", "iw" do |prog|
- Context.prefered_wireless_configuration_program = prog
- end
-
- parser.on "-n network-configuration-program", "--net-conf network-configuration-program", "ifconfig | ip" do |prog|
- Context.prefered_network_configuration_program = prog
- end
-
- parser.on "-d dhcp-client-program", "--dhcp-client dhcp-client-program", "udhcpc | dhclient" do |prog|
- Context.prefered_dhcp_client = prog
- end
-
- parser.on "-r root", "--root root", "Root where to search for /etc/hostname.* files." do |optsn|
- Context.root = optsn
- end
-
- parser.on "-f file", "--file file", "Parse a configuration file." do |optsn|
- file_option = optsn
- end
-
- # 0: nothing is printed, 1: only events, 2: events and messages
- parser.on "-v verbosity", "--verbosity verbosity", "Verbosity (0-2). Default: 1" do |optsn|
- Context.verbosity = optsn.to_i
- end
-
- parser.missing_option do |opt|
- STDERR.puts "You missed the argument for option #{opt}"
- # TODO: explain the different arguments
- exit 1
- end
-
- parser.invalid_option do |flag|
- STDERR.puts "Error: #{flag} not a valid option"
- exit 1
- end
-
- parser.unknown_args do |arg|
- Context.command = arg.shift
- Context.args = arg
-
- case Context.command
- when /^(list)/
- when /^(up)/
- when /^(down)/
- when /^(scan)/
- else
- STDERR.puts "Command #{Context.command} not understood"
- exit 1
- end
-
- # unless arg.empty?
- # STDERR.puts "unknown arg: #{arg}"
- # exit 1
- # end
- end
-
- parser.on "-h", "--help", "Show this help" do
- puts parser
- exit 0
- end
-end
-
-class Do < Process
- class_property simulation = false
-
- def self.run(cmd : String, params : Array(String) = nil)
- if @@simulation
- puts "simulation, do: #{cmd} #{params.join(" ")}"
- Process::Status.new 0
- else
- Process.run cmd, params
- end
- end
-
- def self.run(cmd : String, params : Array(String) = nil, &block : Process -> _)
- if @@simulation
- puts "simulation, do: #{cmd} #{params.join(" ")}"
- Process::Status.new 0
- else
- Process.run cmd, params, &block
- end
- end
-end
-
-class NotSetup
- def to_s(io : IO)
- io << "not setup"
- end
-end
-
-class Autoconfiguration
- def to_s(io : IO)
- io << "autoconfiguration"
- end
-end
-
-class DHCP
- def to_s(io : IO)
- io << "dhcp"
- end
-end
-
-
-class NetworkCommands
- class_property cmd_network_configuration : IfconfigCommand.class | IPCommand.class = IfconfigCommand
- class_property cmd_wireless_configuration : IfconfigCommand.class | IWCommand.class | NotSetup.class = NotSetup
- class_property cmd_dhcp_client : UDHCPCCommand.class | DHClientCommand.class | NotSetup.class = NotSetup
-
- class DNS
- def initialize(@addresses : Array(String), @search : Array(String))
- end
-
- def execute
- File.open("#{Context.root}/etc/resolv.conf", "w") do |file|
- @addresses.each do |address|
- file.puts "nameserver #{address}\n"
- end
-
- file.puts "search #{@search.join(" ")}"
- end
- end
- end
-
- class IWCommand
- # get the available SSID
- def self.scan(ifname : String) : Array(String)
- ssids = Array(String).new
-
- Do.run("iw", [ ifname, "scan" ]) do |p|
- p.output.each_line do |line|
- ssid = /SSID: (?[a-zA-Z0-9_-]+)/.match(line).try &.["ssid"]
-
- unless ssid.nil?
- ssids << ssid
- end
- end
- end
-
- if ssids.empty?
- raise "(iw) cannot get ssid list from #{ifname}"
- end
-
- ssids
- end
- end
-
- class UDHCPCCommand
- def self.run(ifname : String)
- unless Do.run("udhcpc", [ ifname ]).success?
- raise "(udhcpc) dhcp failed on #{ifname}"
- end
- end
-
- def self.dhcp6(ifname : String)
- # we suppose udhcpc6 installed along with udhcpc
- unless Do.run("udhcpc6", [ ifname ]).success?
- raise "(udhcpc6) dhcp failed on #{ifname}"
- end
- end
- end
-
- class DHClientCommand
- def self.run(ifname : String)
- unless Do.run("dhclient", [ ifname ]).success?
- raise "(dhclient) dhcp failed on #{ifname}"
- end
- end
-
- def self.dhcp6(ifname : String)
- unless Do.run("dhclient", [ "-6", ifname ]).success?
- raise "(dhclient) dhcp6 failed on #{ifname}"
- end
- end
- end
-
- class IfconfigCommand
- def self.interface_exists(name : String)
- Do.run("ifconfig", [ name ]).success?
- end
-
- def self.up_or_down(name : String, updown : String)
- unless Do.run("ifconfig", [ name, updown ]).success?
- raise "(ifconfig) Cannot set #{updown} link name #{name}"
- end
- end
- def self.up(name : String)
- self.up_or_down name, "up"
- end
- def self.down(name : String)
- self.up_or_down name, "down"
- end
-
- def self.set_ip(name : String, ip : IPAddress)
- unless Do.run("ifconfig", [ name, ip.to_string ]).success?
- raise "(ifconfig) Cannot set ip address #{ip.to_string} for #{name}"
- end
- end
-
- # currently, aliasses with ifconfig: ifconfig add ip/mask
- # same command as the ip setup
- def self.set_alias(name : String, ip : IPAddress)
- unless Do.run("ifconfig", [ name, "add", ip.to_s ]).success?
- raise "(ifconfig) Cannot set ip address alias #{ip.to_s} for #{name}"
- end
- end
-
- def self.mtu(name : String, mtu : Int32?)
- unless Do.run("ifconfig", [ name, "mtu", mtu.to_s ]).success?
- raise "(ifconfig) Cannot set mtu #{mtu} for #{name}"
- end
- end
-
- def self.description(name : String, description : String)
- unless Do.run("ifconfig", [ name, "description", "\"#{description}\"" ]).success?
- raise "(ifconfig) Cannot set description #{description} for #{name}"
- end
- end
-
- def self.flush(name : String)
- unless Do.run("ifconfig", [ name, "0.0.0.0" ]).success?
- raise "(ifconfig) Cannot flush #{name} ip addresses"
- end
- end
-
- def self.down(name : String)
- unless Do.run("ifconfig", [ name, "down" ]).success?
- raise "(ifconfig) Cannot set down #{name}"
- end
- end
-
- # ifconfig also performs wireless configuration on some OSs
- def self.scan(name : String)
- puts "TODO: (ifconfig) ifconfig ifname scan | grep SSID"
- end
- end
-
- class IPCommand
- def self.interface_exists(name : String)
- Do.run("ip", [ "link", "show", "dev", name ]).success?
- end
-
- def self.up_or_down(name : String, updown : String)
- unless Do.run("ip", [ "link", "set", updown, "dev", name ]).success?
- raise "(ip) Cannot set #{updown} link name #{name}"
- end
- end
- def self.up(name : String)
- self.up_or_down name, "up"
- end
- def self.down(name : String)
- self.up_or_down name, "down"
- end
-
- def self.set_ip(name : String, ip : IPAddress)
- unless Do.run("ip", [ "address", "add", ip.to_string, "dev", name ]).success?
- raise "(ip) Cannot add ip address #{ip.to_string} to #{name}"
- end
- end
-
- def self.set_alias(name : String, ip : IPAddress)
- unless Do.run("ip", [ "address", "add", ip.to_string, "dev", name ]).success?
- raise "(ip) Cannot add ip address alias #{ip.to_string} to #{name}"
- end
- end
-
- def self.mtu(name : String, mtu : Int32?)
- unless Do.run("ip", [ "link", "set", "mtu", mtu.to_s, "dev", name ]).success?
- raise "(ip) Cannot set mtu #{mtu} to #{name}"
- end
- end
-
- def self.flush(name : String)
- unless Do.run("ip", [ "address", "flush", "dev", name ]).success?
- raise "(ip) Cannot flush #{name} ip addresses"
- end
- end
-
- def self.down(name : String)
- unless Do.run("ip", [ "link", "set", "down", "dev", name ]).success?
- raise "(ip) Cannot set down #{name}"
- end
- end
-
- def self.description(name : String, description : String)
- puts "TODO: (ip) setup description '#{description}' to interface #{name}"
- # unless Do.run("ip", [ "link", "set", "description", description, "dev", name ]).success?
- # raise "(ip) Cannot set description #{description} to #{name}"
- # end
- end
- end
-
- def self.interface_exists(name : String)
- @@cmd_network_configuration.interface_exists(name)
- end
-
- def self.up(ifname : String)
- @@cmd_network_configuration.up(ifname)
- end
-
- def self.down(ifname : String)
- @@cmd_network_configuration.up(ifname)
- end
-
- def self.set_ip(name : String, ip : IPAddress)
- @@cmd_network_configuration.set_ip name, ip
- end
-
- def self.dhcp(name : String)
- cmd = @@cmd_dhcp_client
- case cmd
- when NotSetup.class
- puts "no dhcp client: cannot perform dhcp on #{name}"
- else
- cmd.run name
- end
- end
-
- def self.set_alias(name : String, ip : IPAddress)
- @@cmd_network_configuration.set_alias name, ip
- end
-
- def self.mtu(name : String, mtu : Int32?)
- @@cmd_network_configuration.mtu name, mtu
- end
-
- def self.description(name : String, description : String)
- @@cmd_network_configuration.description name, description
- end
-
- def self.flush(name : String)
- @@cmd_network_configuration.flush name
- end
-
- def self.down(name : String)
- @@cmd_network_configuration.down name
- end
-
- def self.scan(ifname : String)
- cmd = @@cmd_wireless_configuration
- case cmd
- when NotSetup.class
- puts "no wireless configuration program: cannot list ssid"
- else
- cmd.scan ifname
- end
- end
-
- def self.autoconfiguration(ifname : String)
- puts "TODO: IPv6 autoconfiguration setup"
- puts "sysctl -w net.ipv6.conf.#{ifname}.autoconf=1"
- puts "sysctl -w net.ipv6.conf.#{ifname}.accept_ra=1"
- end
-
- def self.dhcp6(ifname : String)
- puts "TODO: dhcpv6"
- @@cmd_dhcp_client.dhcp6 ifname
- end
-
- def self.wireless_connect_wpa_psk(ifname : String, ssid : String, passwd : String)
- cmd = @@cmd_wireless_configuration
- case cmd
- when NotSetup.class
- puts "no wireless configuration program: cannot connect to ssid #{ssid}"
- else
- cmd.list_ssid ifname
- end
- end
-end
-
-
-class WirelessAPSetup
- property ssid : String
-
- # This is a list of parameters that should be unique to each AP
- property up : Bool
- property description : String?
- property mtu : Int32?
- property main_ip_v4 : IPAddress | DHCP | NotSetup
- property main_ip_v6 : IPAddress | DHCP | Autoconfiguration | NotSetup
- property aliasses_v4 : Array(IPAddress)
- property aliasses_v6 : Array(IPAddress)
- property dns : Array(IPAddress)
-
- # we currently only support WPA2-PSK wireless security mechanism
- property security : WPA
-
- class WPA
- property key : String
- def initialize(@key)
- end
- end
-
- def initialize(@ssid, @security)
- @main_ip_v4 = NotSetup.new
- @main_ip_v6 = NotSetup.new
- @aliasses_v4 = Array(IPAddress).new
- @aliasses_v6 = Array(IPAddress).new
- @up = true
- @dns = Array(IPAddress).new
- end
-
-
- def to_s(io : IO)
- io << to_string
- end
-
- def to_string
- String.build do |str|
- str << "\t#{CBLUE}#{ssid}#{CRESET}\n"
-
- str << "\t\t#description #{description.not_nil!}\n" unless description.nil?
- str << "\t\t#{@up? "up" : "down"}\n"
- str << "\t\tmtu #{mtu}\n" unless mtu.nil?
-
- # ipv4
- unless main_ip_v4.is_a?(NotSetup)
- str << "\t\tinet #{main_ip_v4}\n"
-
- aliasses_v4.each do |a|
- str << "\t\talias #{a}\n"
- end
- end
-
- # ipv6
- unless main_ip_v6.is_a?(NotSetup)
- str << "\t\tinet6 #{main_ip_v6}\n"
-
- @aliasses_v6.each do |a|
- str << "\t\talias6 #{a}\n"
- end
- end
-
- dns.each do |ip|
- str << "\t\tdns: #{ip}\n"
- end
-
- # to improve readability
- str << "\n"
- end
- end
-end
-
-
-#
-# interface configuration
-#
-
-class InterfaceConfiguration
-
- property name : String
- property up : Bool
- property description : String?
- property mtu : Int32?
- property wireless : Bool
- property main_ip_v4 : IPAddress | DHCP | NotSetup
- property main_ip_v6 : IPAddress | Autoconfiguration | DHCP | NotSetup
- property aliasses_v4 : Array(IPAddress)
- property aliasses_v6 : Array(IPAddress)
- property wireless_networks : Hash(String, WirelessAPSetup)
- property dns : Array(IPAddress)
-
- def initialize (@name, @up,
- @description,
- @mtu,
- @main_ip_v4, @main_ip_v6, aliasses,
- @wireless, @wireless_networks,
- @dns = Array(IPAddress).new
- )
-
- @aliasses_v4 = Array(IPAddress).new
- @aliasses_v6 = Array(IPAddress).new
-
- aliasses.each do |ip|
- if ip.ipv4?
- aliasses_v4 << ip
- else
- aliasses_v6 << ip
- end
- end
- end
-
- def to_s(io : IO)
- io << to_string
- end
-
- def to_string
- String.build do |str|
- if NetworkCommands.interface_exists(@name)
- str << "#{CGREEN}#{@name}#{CRESET}\n"
- else
- str << "#{CRED}#{@name}#{CRESET}\n"
- end
-
- str << "\tdescription: '#{description.not_nil!}'\n" unless description.nil?
-
- str << "\t#{@up? "up" : "down"}\n"
-
- unless mtu.nil?
- str << "\tmtu #{mtu}\n"
- end
-
- # ipv4
- unless main_ip_v4.is_a?(NotSetup)
- str << "\tinet #{main_ip_v4}\n"
-
- aliasses_v4.each do |a|
- str << "\talias #{a}\n"
- end
- end
-
- # warning: alias but no main ip address
- if main_ip_v4.is_a?(NotSetup) && ! aliasses_v4.empty?
- str << "\t#{CRED}alias configured but no main ipv4 configuration.#{CRESET}\n"
- str << "\t#{CRED}Should main ipv4 be obtained from DHCP? Static configuration?#{CRESET}\n"
- end
-
- # ipv6
- unless main_ip_v6.is_a?(NotSetup)
- str << "\tinet6 #{main_ip_v6}\n"
-
- aliasses_v6.each do |a|
- str << "\talias6 #{a}\n"
- end
- end
-
- # warning: alias but no main ip address
- if main_ip_v6.is_a?(NotSetup) && ! aliasses_v6.empty?
- str << "\t#{CRED}alias6 configured but no main ipv6 configuration.#{CRESET}\n"
- str << "\t#{CRED}Should main ipv6 be obtained from autoconfiguration? DHCP? Static configuration?#{CRESET}\n"
- end
-
- unless dns.empty?
- dns.each do |ip|
- str << "\tdns: #{ip}\n"
- end
- end
-
- unless wireless_networks.empty?
- # to improve readability
- str << "\n"
- wireless_networks.each do |k,v|
- str << v
- end
- end
- end
- end
-
- # configure the interface
- def execute
- unless NetworkCommands.interface_exists(@name)
- raise "The interface #{@name} doesn't exists, yet."
- end
-
- # TODO: treat differently wireless and non-wireless interfaces
- if @wireless
- puts "interface #{name} is wireless"
- puts "TODO:"
- puts "1. scan for SSID"
- puts "2. select configured SSID then try to connect"
- puts "3. connectivity check with the gateway"
- else
- puts "interface #{name} is not wireless"
- end
-
- if up
- NetworkCommands.up name
- else
- puts "not marked as 'up' -- ending here"
- return
- end
-
- unless mtu.nil?
- NetworkCommands.mtu name, mtu
- end
-
- unless description.nil?
- NetworkCommands.description name, description.not_nil!
- end
-
- # ipv4 configuration
- main_ip_v4.tap do |ip|
- case ip
- when IPAddress
- NetworkCommands.set_ip name, ip
- when DHCP
- NetworkCommands.dhcp name
- when NotSetup
- # do nothing
- else
- raise "ipv4 configuration: neither static nor dynamic"
- end
-
- # We wont setup aliasses unless there is an actual IP address
- if ip != NotSetup
- aliasses_v4.each do |ip_alias|
- NetworkCommands.set_alias name, ip_alias
- end
- end
- end
-
- # ipv6 configuration
- main_ip_v6.tap do |ip|
- case ip
- when IPAddress
- NetworkCommands.set_ip name, ip
- when Autoconfiguration
- NetworkCommands.autoconfiguration name
- when DHCP
- NetworkCommands.dhcp6 name
- when NotSetup
- # do nothing
- else
- raise "ipv6 configuration: neither static nor dynamic"
- end
-
- # We wont setup aliasses unless there is an actual IP address
- if ip != NotSetup
- aliasses_v6.each do |ip_alias|
- NetworkCommands.set_alias name, ip_alias
- end
- end
- end
- end
-
- def down
- unless NetworkCommands.interface_exists(@name)
- raise "The interface #{@name} doesn't exists or is already down."
- end
-
- NetworkCommands.flush name
- NetworkCommands.down name
- end
-
- def scan
- unless NetworkCommands.interface_exists(@name)
- raise "The interface #{@name} doesn't exists or is already down."
- end
-
- ssid_list = NetworkCommands.scan name
- pp! ssid_list
- end
-end
-
-class NetworkConfigurationParser
- def self.parse_file(file_name : String) : InterfaceConfiguration
- content = File.read(file_name)
- content = content.rchop
- ifname = /.([a-zA-Z0-9]+)$/.match(file_name).try &.[1]
- if ifname.nil?
- raise "The interface name is not known from the filename: '#{file_name}'"
- end
-
- wireless = false
- wireless = true unless /^wl[0-9]+$/.match(ifname)
- self.parse(ifname.not_nil!, content, wireless)
- end
-
- def self.parse (ifname : String, data : String, wireless = false) : InterfaceConfiguration
- up = false
- description = nil
- mtu = nil
- main_ip_v4 = NotSetup.new
- main_ip_v6 = NotSetup.new
-
- dns = Array(IPAddress).new
-
- aliasses = [] of IPAddress
-
- wireless_networks = {} of String => WirelessAPSetup
-
- data.split("\n").each do |line|
- case line
- when /^up/
- up = true
- when /^description/
- description = /^description (.+)/.match(line).try &.[1]
- when /^inet6? alias .*/
- ipstr = /^inet6? alias ([a-f0-9:.\/]+)/.match(line).try &.[1]
- if ipstr.nil?
- puts "wrong IP address alias, line #{line}"
- next
- end
- aliasses.push IPAddress.parse(ipstr)
- when /^inet6? dhcp/
- # IP address is DHCP
- if /^inet /.match(line)
- main_ip_v4 = DHCP.new
- else
- main_ip_v6 = DHCP.new
- end
- when /^inet6 autoconf/
- # IP address is autoconfigured
- main_ip_v6 = Autoconfiguration.new
-
- when /^inet6? .*/
- ipstr = /^inet6? ([a-f0-9:.\/]+)/.match(line).try &.[1]
- if ipstr.nil?
- puts "wrong IP address, line #{line}"
- next
- end
- if /^inet /.match(line)
- main_ip_v4 = IPAddress.parse ipstr
- else
- main_ip_v6 = IPAddress.parse ipstr
- end
- when /^join [^ \t]+ wpakey .*/
- # WPA2-PSK, other security mechanisms are not supported, yet
- ssid = /^join ([^ \t]+)/.match(line).try &.[1]
- wpakeystr = /^join [^ \t]+ wpakey ([^ \t]+)/.match(line).try &.[1]
-
- if ssid.nil?
- puts "wrong SSID in line: #{line}"
- next
- end
-
- if wpakeystr.nil?
- puts "wrong wpa key in line: #{line}"
- next
- end
-
- # TODO
- new_ap = WirelessAPSetup.new ssid, WirelessAPSetup::WPA.new(wpakeystr)
- wireless_networks[ssid] = new_ap
-
-
- when /^network [^ \t]+ inet6 autoconf/
- puts "TODO: network SSID inet6 autoconf"
-
- ssid = /^network ([^ \t]+)/.match(line).try &.[1]
- ipstr = /^network [^ \t]+ inet6? ([^ \t]+)/.match(line).try &.[1]
-
- if ssid.nil?
- puts "wrong SSID in line: #{line}"
- next
- end
-
- if ipstr.nil?
- puts "wrong ip address in line: #{line}"
- next
- end
-
- access_point = wireless_networks[ssid].not_nil!
- access_point.main_ip_v6 = Autoconfiguration.new
- puts "for SSID: #{ssid} ipv6 configuration = autoconf"
-
- when /^network [^ \t]+ inet6? .*/
- ssid = nil
- ipstr = nil
-
- /^network (?[^ \t]+) inet6? (?[^ \t]+)/.match(line).try do |m|
- ssid = m["ssid"]
- ipstr = m["ip"]
- end
-
- if ssid.nil?
- puts "wrong SSID in line: #{line}"
- next
- end
-
- if ipstr.nil?
- puts "wrong ip address in line: #{line}"
- next
- end
-
- ipaddr = IPAddress.parse ipstr
- access_point = wireless_networks[ssid].not_nil!
-
- if ipaddr.ipv4?
- access_point.main_ip_v4 = ipaddr
- elsif ipaddr.ipv6?
- access_point.main_ip_v6 = ipaddr
- else
- puts "wrong ip address in line: #{line} (neither ipv4 or ipv6)"
- end
-
- when /^network [^ ]+ dhcp6?/
- ssid = /^network (?[^ \t]+)/.match(line).try &.["ssid"]
-
- if ssid.nil?
- puts "wrong SSID in line: #{line}"
- next
- end
-
- access_point = wireless_networks[ssid].not_nil!
-
- if /dhcp6/.match(line)
- access_point.main_ip_v6 = DHCP.new
- elsif /dhcp/.match(line)
- access_point.main_ip_v4 = DHCP.new
- else
- puts "wrong dhcp instruction in line: #{line}"
- end
-
- when /^network [^ ]+ dns .*/
- ssid = nil
- ipstr = nil
-
- /^network (?[^ \t]+) dns (?[^ \t]+)/.match(line).try do |m|
- ssid = m["ssid"]
- ipstr = m["ip"]
- end
-
- if ssid.nil?
- puts "wrong SSID in line: #{line}"
- next
- end
-
- if ipstr.nil?
- puts "wrong ip address in line: #{line}"
- next
- end
-
- access_point = wireless_networks[ssid].not_nil!
- ipaddr = IPAddress.parse ipstr
- access_point.dns << ipaddr
-
- when /^mtu [0-9]+/
- mtu = /^mtu ([0-9]+)/.match(line).try &.[1].to_i
-
- when /^dns [^ \t]+/
- ipstr = nil
-
- /^dns (?[^ \t]+)/.match(line).try do |m|
- ipstr = m["ip"]
- end
-
- if ipstr.nil?
- puts "wrong ip address in line: #{line}"
- next
- end
-
- ipaddr = IPAddress.parse ipstr
- dns << ipaddr
-
- when /^#.*$/
- # simple comment
- when /^[ \t]*$/
- # empty line
- else
- raise "Cannot parse: #{line}"
- end
- end
-
- InterfaceConfiguration.new(ifname, up,
- description,
- mtu,
- main_ip_v4, main_ip_v6,
- aliasses,
- wireless, wireless_networks,
- dns)
- end
-end
-
-
-
-class Autodetect
- class_property print_autodetect : Bool = false
-
- def self.which(cmd : String)
- if Process.run("which", [ cmd ]).success?
- puts "#{cmd} installed" if print_autodetect
- true
- else
- puts "#{cmd} not installed" if print_autodetect
- false
- end
- end
-end
-
-
-Do.simulation = Context.simulation
-Autodetect.print_autodetect = Context.print_autodetect
-
-#
-# discover available configuration commands
-#
-
-# ifconfig = *bsd and some linux
-# ip = linux
-possible_network_configuration_cmds = {
- "ifconfig" => NetworkCommands::IfconfigCommand,
- "ip" => NetworkCommands::IPCommand
-}
-
-# udhcpc = busybox dhcp client
-possible_dhcp_clients = {
- "udhcpc" => NetworkCommands::UDHCPCCommand,
- "dhclient" => NetworkCommands::DHClientCommand
-}
-
-# iw = linux
-possible_wireless_configuration_cmds = {
- "iw" => NetworkCommands::IWCommand,
- "ifconfig" => NetworkCommands::IfconfigCommand
-}
-
-key = Context.prefered_network_configuration_program
-key = possible_network_configuration_cmds.keys.find { |key| Autodetect.which(key) } if key.nil?
-# should crash if there is no network command installed
-NetworkCommands.cmd_network_configuration = possible_network_configuration_cmds[key.not_nil!]
-
-key = Context.prefered_dhcp_client
-key = possible_dhcp_clients.keys.find { |key| Autodetect.which(key) } if key.nil?
-# should not crash if there is no
-NetworkCommands.cmd_dhcp_client = possible_dhcp_clients[key] unless key.nil?
-
-key = Context.prefered_wireless_configuration_program
-key = possible_wireless_configuration_cmds.keys.find { |key| Autodetect.which(key) } if key.nil?
-# should crash if there is no wireless command installed
-NetworkCommands.cmd_wireless_configuration = possible_wireless_configuration_cmds[key.not_nil!]
-
-
-
-files = Array(String).new
-Dir.children("#{Context.root}/etc/").each do |f|
- if /^hostname\./.match(f)
- files << f
- end
-end
-
-interface_files = Array(String).new
-
-if ! file_option.nil?
- # file passed via the '-f' option
- # TODO: why having to force "not_nil!" ? Seems like a compiler bug
- interface_files << file_option.not_nil!
-elsif Context.args.empty?
- # every configured interface
- files.each do |f|
- interface_files << "#{Context.root}/etc/#{f}"
- end
-else
- # only interfaces in arguments
- Context.args.each do |interface|
- interface_files << "#{Context.root}/etc/hostname.#{interface}"
- end
-end
-
-
-
-begin
- case Context.command
- when "list"
- interface_files.each do |f|
- puts NetworkConfigurationParser.parse_file(f)
- end
- when "up"
- interface_files.each do |f|
- network_configuration = NetworkConfigurationParser.parse_file(f)
- network_configuration.execute
- end
- when "down"
- interface_files.each do |f|
- network_configuration = NetworkConfigurationParser.parse_file(f)
- network_configuration.down
- end
- when "scan"
- interface_files.each do |f|
- network_configuration = NetworkConfigurationParser.parse_file(f)
- network_configuration.scan
- end
- end
-rescue e
- STDERR.puts "#{CRED}Exception: #{CRESET}#{e}"
-end
+require "./context"
+require "./network_commands"
+require "./network_configuration_parser"
+require "./do"
+require "./configuration"
+require "./autodetect_environment"
+require "./cli"
diff --git a/src/network_commands.cr b/src/network_commands.cr
new file mode 100644
index 0000000..1365282
--- /dev/null
+++ b/src/network_commands.cr
@@ -0,0 +1,280 @@
+
+# TODO: OpenBSD: no '-w' parameter for sysctl
+# TODO: OpenBSD: ifconfig scan
+# TODO: Linux: description with ip-* command family
+
+class NetworkCommands
+ class_property cmd_network_configuration : IfconfigCommand.class | IPCommand.class = IfconfigCommand
+ class_property cmd_wireless_configuration : IfconfigCommand.class | IWCommand.class | NotSetup.class = NotSetup
+ class_property cmd_dhcp_client : UDHCPCCommand.class | DHClientCommand.class | NotSetup.class = NotSetup
+
+ class DNS
+ def initialize(@addresses : Array(String), @search : Array(String))
+ end
+
+ def execute
+ File.open("#{Context.root}/etc/resolv.conf", "w") do |file|
+ @addresses.each do |address|
+ file.puts "nameserver #{address}\n"
+ end
+
+ file.puts "search #{@search.join(" ")}"
+ end
+ end
+ end
+
+ class IWCommand
+ # get the available SSID
+ def self.scan(ifname : String) : Array(String)
+ ssids = Array(String).new
+
+ Do.run("iw", [ ifname, "scan" ]) do |p|
+ p.output.each_line do |line|
+ ssid = /SSID: (?[a-zA-Z0-9_-]+)/.match(line).try &.["ssid"]
+
+ unless ssid.nil?
+ ssids << ssid
+ end
+ end
+ end
+
+ if ssids.empty?
+ raise "(iw) cannot get ssid list from #{ifname}"
+ end
+
+ ssids
+ end
+ end
+
+ class UDHCPCCommand
+ def self.run(ifname : String)
+ unless Do.run("udhcpc", [ ifname ]).success?
+ raise "(udhcpc) dhcp failed on #{ifname}"
+ end
+ end
+
+ def self.dhcp6(ifname : String)
+ # we suppose udhcpc6 installed along with udhcpc
+ unless Do.run("udhcpc6", [ ifname ]).success?
+ raise "(udhcpc6) dhcp failed on #{ifname}"
+ end
+ end
+ end
+
+ class DHClientCommand
+ def self.run(ifname : String)
+ unless Do.run("dhclient", [ ifname ]).success?
+ raise "(dhclient) dhcp failed on #{ifname}"
+ end
+ end
+
+ def self.dhcp6(ifname : String)
+ unless Do.run("dhclient", [ "-6", ifname ]).success?
+ raise "(dhclient) dhcp6 failed on #{ifname}"
+ end
+ end
+ end
+
+ class IfconfigCommand
+ def self.interface_exists(name : String)
+ Do.run("ifconfig", [ name ]).success?
+ end
+
+ def self.up_or_down(name : String, updown : String)
+ unless Do.run("ifconfig", [ name, updown ]).success?
+ raise "(ifconfig) Cannot set #{updown} link name #{name}"
+ end
+ end
+ def self.up(name : String)
+ self.up_or_down name, "up"
+ end
+ def self.down(name : String)
+ self.up_or_down name, "down"
+ end
+
+ def self.set_ip(name : String, ip : IPAddress)
+ unless Do.run("ifconfig", [ name, ip.to_string ]).success?
+ raise "(ifconfig) Cannot set ip address #{ip.to_string} for #{name}"
+ end
+ end
+
+ # currently, aliasses with ifconfig: ifconfig add ip/mask
+ # same command as the ip setup
+ def self.set_alias(name : String, ip : IPAddress)
+ unless Do.run("ifconfig", [ name, "add", ip.to_s ]).success?
+ raise "(ifconfig) Cannot set ip address alias #{ip.to_s} for #{name}"
+ end
+ end
+
+ def self.mtu(name : String, mtu : Int32?)
+ unless Do.run("ifconfig", [ name, "mtu", mtu.to_s ]).success?
+ raise "(ifconfig) Cannot set mtu #{mtu} for #{name}"
+ end
+ end
+
+ def self.description(name : String, description : String)
+ unless Do.run("ifconfig", [ name, "description", "\"#{description}\"" ]).success?
+ raise "(ifconfig) Cannot set description #{description} for #{name}"
+ end
+ end
+
+ def self.flush(name : String)
+ unless Do.run("ifconfig", [ name, "0.0.0.0" ]).success?
+ raise "(ifconfig) Cannot flush #{name} ip addresses"
+ end
+ end
+
+ def self.down(name : String)
+ unless Do.run("ifconfig", [ name, "down" ]).success?
+ raise "(ifconfig) Cannot set down #{name}"
+ end
+ end
+
+ # ifconfig also performs wireless configuration on some OSs
+ def self.scan(name : String)
+ puts "TODO: (ifconfig) ifconfig ifname scan | grep SSID"
+ end
+ end
+
+ class IPCommand
+ def self.interface_exists(name : String)
+ Do.run("ip", [ "link", "show", "dev", name ]).success?
+ end
+
+ def self.up_or_down(name : String, updown : String)
+ unless Do.run("ip", [ "link", "set", updown, "dev", name ]).success?
+ raise "(ip) Cannot set #{updown} link name #{name}"
+ end
+ end
+ def self.up(name : String)
+ self.up_or_down name, "up"
+ end
+ def self.down(name : String)
+ self.up_or_down name, "down"
+ end
+
+ def self.set_ip(name : String, ip : IPAddress)
+ unless Do.run("ip", [ "address", "add", ip.to_string, "dev", name ]).success?
+ raise "(ip) Cannot add ip address #{ip.to_string} to #{name}"
+ end
+ end
+
+ def self.set_alias(name : String, ip : IPAddress)
+ unless Do.run("ip", [ "address", "add", ip.to_string, "dev", name ]).success?
+ raise "(ip) Cannot add ip address alias #{ip.to_string} to #{name}"
+ end
+ end
+
+ def self.mtu(name : String, mtu : Int32?)
+ unless Do.run("ip", [ "link", "set", "mtu", mtu.to_s, "dev", name ]).success?
+ raise "(ip) Cannot set mtu #{mtu} to #{name}"
+ end
+ end
+
+ def self.flush(name : String)
+ unless Do.run("ip", [ "address", "flush", "dev", name ]).success?
+ raise "(ip) Cannot flush #{name} ip addresses"
+ end
+ end
+
+ def self.down(name : String)
+ unless Do.run("ip", [ "link", "set", "down", "dev", name ]).success?
+ raise "(ip) Cannot set down #{name}"
+ end
+ end
+
+ def self.description(name : String, description : String)
+ puts "TODO: (ip) setup description '#{description}' to interface #{name}"
+ # unless Do.run("ip", [ "link", "set", "description", description, "dev", name ]).success?
+ # raise "(ip) Cannot set description #{description} to #{name}"
+ # end
+ end
+ end
+
+ def self.interface_exists(name : String)
+ @@cmd_network_configuration.interface_exists(name)
+ end
+
+ def self.up(ifname : String)
+ @@cmd_network_configuration.up(ifname)
+ end
+
+ def self.down(ifname : String)
+ @@cmd_network_configuration.up(ifname)
+ end
+
+ def self.set_ip(name : String, ip : IPAddress)
+ @@cmd_network_configuration.set_ip name, ip
+ end
+
+ def self.dhcp(name : String)
+ cmd = @@cmd_dhcp_client
+ case cmd
+ when NotSetup.class
+ raise "no dhcp client: cannot perform dhcp on #{name}"
+ else
+ cmd.run name
+ end
+ end
+
+ def self.set_alias(name : String, ip : IPAddress)
+ @@cmd_network_configuration.set_alias name, ip
+ end
+
+ def self.mtu(name : String, mtu : Int32?)
+ @@cmd_network_configuration.mtu name, mtu
+ end
+
+ def self.description(name : String, description : String)
+ @@cmd_network_configuration.description name, description
+ end
+
+ def self.flush(name : String)
+ @@cmd_network_configuration.flush name
+ end
+
+ def self.down(name : String)
+ @@cmd_network_configuration.down name
+ end
+
+ def self.scan(ifname : String)
+ cmd = @@cmd_wireless_configuration
+ case cmd
+ when NotSetup.class
+ raise "no wireless configuration program: cannot list ssid"
+ else
+ cmd.scan ifname
+ end
+ end
+
+ def self.autoconfiguration(ifname : String)
+ # TODO: no '-w' option on openbsd
+ unless Do.run("sysctl", [ "-w", "net.ipv6.conf.#{ifname}.autoconf=1" ]).success?
+ raise "(sysctl) cannot set 'net.ipv6.conf.#{ifname}.autoconf=1'"
+ end
+
+ unless Do.run("sysctl", [ "-w", "net.ipv6.conf.#{ifname}.accept_ra=1" ]).success?
+ raise "(sysctl) cannot set 'net.ipv6.conf.#{ifname}.accept_ra=1'"
+ end
+ end
+
+ def self.dhcp6(ifname : String)
+ cmd = @@cmd_dhcp_client
+ case cmd
+ when NotSetup.class
+ raise "no dhcpv6 program"
+ else
+ cmd.dhcp6 ifname
+ end
+ end
+
+ def self.wireless_connect_wpa_psk(ifname : String, ssid : String, passwd : String)
+ cmd = @@cmd_wireless_configuration
+ case cmd
+ when NotSetup.class
+ puts "no wireless configuration program: cannot connect to ssid #{ssid}"
+ else
+ cmd.list_ssid ifname
+ end
+ end
+end
diff --git a/src/network_configuration_parser.cr b/src/network_configuration_parser.cr
new file mode 100644
index 0000000..4c3f33b
--- /dev/null
+++ b/src/network_configuration_parser.cr
@@ -0,0 +1,210 @@
+
+class NetworkConfigurationParser
+ def self.parse_file(file_name : String) : InterfaceConfiguration
+ content = File.read(file_name)
+ content = content.rchop
+ ifname = /.([a-zA-Z0-9]+)$/.match(file_name).try &.[1]
+ if ifname.nil?
+ raise "The interface name is not known from the filename: '#{file_name}'"
+ end
+
+ wireless = false
+ wireless = true unless /^wl[0-9]+$/.match(ifname)
+ self.parse(ifname.not_nil!, content, wireless)
+ end
+
+ def self.parse (ifname : String, data : String, wireless = false) : InterfaceConfiguration
+ up = false
+ description = nil
+ mtu = nil
+ main_ip_v4 = NotSetup.new
+ main_ip_v6 = NotSetup.new
+
+ dns = Array(IPAddress).new
+
+ aliasses = [] of IPAddress
+
+ wireless_networks = {} of String => WirelessAPSetup
+
+ data.split("\n").each do |line|
+ case line
+ when /^up/
+ up = true
+ when /^description/
+ description = /^description (.+)/.match(line).try &.[1]
+ when /^inet6? alias .*/
+ ipstr = /^inet6? alias ([a-f0-9:.\/]+)/.match(line).try &.[1]
+ if ipstr.nil?
+ puts "wrong IP address alias, line #{line}"
+ next
+ end
+ aliasses.push IPAddress.parse(ipstr)
+ when /^inet6? dhcp/
+ # IP address is DHCP
+ if /^inet /.match(line)
+ main_ip_v4 = DHCP.new
+ else
+ main_ip_v6 = DHCP.new
+ end
+ when /^inet6 autoconf/
+ # IP address is autoconfigured
+ main_ip_v6 = Autoconfiguration.new
+
+ when /^inet6? .*/
+ ipstr = /^inet6? ([a-f0-9:.\/]+)/.match(line).try &.[1]
+ if ipstr.nil?
+ puts "wrong IP address, line #{line}"
+ next
+ end
+ if /^inet /.match(line)
+ main_ip_v4 = IPAddress.parse ipstr
+ else
+ main_ip_v6 = IPAddress.parse ipstr
+ end
+ when /^join [^ \t]+ wpakey .*/
+ # WPA2-PSK, other security mechanisms are not supported, yet
+ ssid = /^join ([^ \t]+)/.match(line).try &.[1]
+ wpakeystr = /^join [^ \t]+ wpakey ([^ \t]+)/.match(line).try &.[1]
+
+ if ssid.nil?
+ puts "wrong SSID in line: #{line}"
+ next
+ end
+
+ if wpakeystr.nil?
+ puts "wrong wpa key in line: #{line}"
+ next
+ end
+
+ # TODO
+ new_ap = WirelessAPSetup.new ssid, WirelessAPSetup::WPA.new(wpakeystr)
+ wireless_networks[ssid] = new_ap
+
+
+ when /^network [^ \t]+ inet6 autoconf/
+ puts "TODO: network SSID inet6 autoconf"
+
+ ssid = /^network ([^ \t]+)/.match(line).try &.[1]
+ ipstr = /^network [^ \t]+ inet6? ([^ \t]+)/.match(line).try &.[1]
+
+ if ssid.nil?
+ puts "wrong SSID in line: #{line}"
+ next
+ end
+
+ if ipstr.nil?
+ puts "wrong ip address in line: #{line}"
+ next
+ end
+
+ access_point = wireless_networks[ssid].not_nil!
+ access_point.main_ip_v6 = Autoconfiguration.new
+ puts "for SSID: #{ssid} ipv6 configuration = autoconf"
+
+ when /^network [^ \t]+ inet6? .*/
+ ssid = nil
+ ipstr = nil
+
+ /^network (?[^ \t]+) inet6? (?[^ \t]+)/.match(line).try do |m|
+ ssid = m["ssid"]
+ ipstr = m["ip"]
+ end
+
+ if ssid.nil?
+ puts "wrong SSID in line: #{line}"
+ next
+ end
+
+ if ipstr.nil?
+ puts "wrong ip address in line: #{line}"
+ next
+ end
+
+ ipaddr = IPAddress.parse ipstr
+ access_point = wireless_networks[ssid].not_nil!
+
+ if ipaddr.ipv4?
+ access_point.main_ip_v4 = ipaddr
+ elsif ipaddr.ipv6?
+ access_point.main_ip_v6 = ipaddr
+ else
+ puts "wrong ip address in line: #{line} (neither ipv4 or ipv6)"
+ end
+
+ when /^network [^ ]+ dhcp6?/
+ ssid = /^network (?[^ \t]+)/.match(line).try &.["ssid"]
+
+ if ssid.nil?
+ puts "wrong SSID in line: #{line}"
+ next
+ end
+
+ access_point = wireless_networks[ssid].not_nil!
+
+ if /dhcp6/.match(line)
+ access_point.main_ip_v6 = DHCP.new
+ elsif /dhcp/.match(line)
+ access_point.main_ip_v4 = DHCP.new
+ else
+ puts "wrong dhcp instruction in line: #{line}"
+ end
+
+ when /^network [^ ]+ dns .*/
+ ssid = nil
+ ipstr = nil
+
+ /^network (?[^ \t]+) dns (?[^ \t]+)/.match(line).try do |m|
+ ssid = m["ssid"]
+ ipstr = m["ip"]
+ end
+
+ if ssid.nil?
+ puts "wrong SSID in line: #{line}"
+ next
+ end
+
+ if ipstr.nil?
+ puts "wrong ip address in line: #{line}"
+ next
+ end
+
+ access_point = wireless_networks[ssid].not_nil!
+ ipaddr = IPAddress.parse ipstr
+ access_point.dns << ipaddr
+
+ when /^mtu [0-9]+/
+ mtu = /^mtu ([0-9]+)/.match(line).try &.[1].to_i
+
+ when /^dns [^ \t]+/
+ ipstr = nil
+
+ /^dns (?[^ \t]+)/.match(line).try do |m|
+ ipstr = m["ip"]
+ end
+
+ if ipstr.nil?
+ puts "wrong ip address in line: #{line}"
+ next
+ end
+
+ ipaddr = IPAddress.parse ipstr
+ dns << ipaddr
+
+ when /^#.*$/
+ # simple comment
+ when /^[ \t]*$/
+ # empty line
+ else
+ raise "Cannot parse: #{line}"
+ end
+ end
+
+ InterfaceConfiguration.new(ifname, up,
+ description,
+ mtu,
+ main_ip_v4, main_ip_v6,
+ aliasses,
+ wireless, wireless_networks,
+ dns)
+ end
+end