require "option_parser" require "ipaddress" require "./colors" simulation = false file = nil OptionParser.parse! do |parser| parser.on "-s", "--simulation", "Export the network configuration." do simulation = true end parser.on "-f file", "--file file", "Parse a configuration file." do |optsn| file = 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| verbosity = optsn.to_i 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 end class NetworkCommands class DHCPCommands def self.dhcp(ifname : String) # TODO: verify which dhcp client is installed on the system cmd = "udhcpc" unless Do.run(cmd, [ name ]).success? raise "(#{cmd}) dhcp failed on #{ifname}" end end end def self.which(cmd : String) Do.run("which", [ cmd ]).success? 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) puts "(ip) setup static IP address" 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) puts "(ip) setup static IP address" end end def self.choose_command : IfconfigCommand.class | IPCommand.class if self.which("ifconfig") IfconfigCommand elsif self.which("ip") IPCommand else raise "Neither ifconfig or ip commands exists on this system" end end def self.interface_exists?(name : String) cmd = self.choose_command cmd.interface_exists?(name) end def self.up(ifname : String) cmd = self.choose_command cmd.up(ifname) end def self.down(ifname : String) cmd = self.choose_command cmd.up(ifname) end def self.set_ip(name : String, ip : IPAddress) puts "(ip) setup static IP address" end def self.dhcp(name : String) puts "(ip) setup dynamic IP address" DHCPCommands.dhcp name end end # # interface configuration # class InterfaceConfiguration class NotSetup def to_s(io : IO) io << "not setup" end end class DHCP def to_s(io : IO) io << "dhcp" end end property name : String property up : Bool property main_ip_v4 : IPAddress | DHCP | NotSetup property main_ip_v6 : IPAddress | DHCP | NotSetup property aliasses_v4 : Array(IPAddress) property aliasses_v6 : Array(IPAddress) def initialize (@name, @up, @main_ip_v4, @main_ip_v6, aliasses) @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 << "\t#{@up? "up" : "down"}\n" str << "\tinet #{@main_ip_v4}\n" unless @aliasses_v4.empty? @aliasses_v4.each do |a| str << "\talias #{a}\n" end end str << "\tinet6 #{@main_ip_v6}\n" unless @aliasses_v6.empty? @aliasses_v6.each do |a| str << "\talias6 #{a}\n" end end end end # configure the interface def execute unless NetworkCommands.interface_exists?(@name) raise "The interface #{@name} doesn't exists, yet." end puts "OK on va configurer ça" puts "#{self}" if @up NetworkCommands.up @name else puts "not marked as 'up' -- ending here" return end case main_ip_v4 = @main_ip_v4 when IPAddress NetworkCommands.set_ip @name, main_ip_v4 when DHCP NetworkCommands.dhcp @name when NotSetup puts "no ipv4" else raise "ipv4 configuration: neither static nor dynamic" end case main_ip_v6 = @main_ip_v6 when IPAddress NetworkCommands.set_ip @name, main_ip_v6 # TODO #when Autoconfiguration # NetworkCommands.autoconfiguration @name #when DHCP # NetworkCommands.dhcp6 @name when NotSetup puts "no ipv4" else raise "ipv4 configuration: neither static nor dynamic" end # str << "\tinet #{@main_ip_v4}\n" # str << "\t#{@up? "up" : "down"}\n" # str << "\tinet #{@main_ip_v4}\n" # unless @aliasses_v4.empty? # @aliasses_v4.each do |a| # str << "\talias #{a}\n" # end # end # str << "\tinet6 #{@main_ip_v6}\n" # @aliasses_v6.each do |a| # str << "\talias6 #{a}\n" # end # alias execution: only when the main ip address is setup 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] self.parse(ifname.not_nil!, content) end def self.parse (ifname : String, data : String) : InterfaceConfiguration up = false main_ip_v4 = InterfaceConfiguration::NotSetup.new main_ip_v6 = InterfaceConfiguration::NotSetup.new aliasses = [] of IPAddress data.split("\n").each do |line| case line when /^up/ up = true 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/ # IPaddress is DHCP if /^inet /.match(line) main_ip_v4 = InterfaceConfiguration::DHCP.new else main_ip_v6 = InterfaceConfiguration::DHCP.new end 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 else raise "Cannot parse: #{line}" end end InterfaceConfiguration.new(ifname, up, main_ip_v4, main_ip_v6, aliasses) end end Do.simulation = simulation if file.nil? raise "Cannot choose files yet" else # TODO: why having to force "not_nil!" ? Seems like a compiler bug NetworkConfigurationParser.parse_file(file.not_nil!).execute end