392 lines
9.0 KiB
Crystal
392 lines
9.0 KiB
Crystal
|
|
require "option_parser"
|
|
require "ipaddress"
|
|
require "./colors"
|
|
|
|
simulation = false
|
|
file = nil
|
|
|
|
prefered_network_configuration_program = nil
|
|
prefered_dhcp_client = nil
|
|
|
|
OptionParser.parse! do |parser|
|
|
parser.on "-s", "--simulation", "Export the network configuration." do
|
|
simulation = true
|
|
end
|
|
|
|
parser.on "-n network-configuration-program", "--net-conf network-configuration-program", "ifconfig | ip" do |prog|
|
|
prefered_network_configuration_program = prog
|
|
end
|
|
|
|
parser.on "-d dhcp-client-program", "--dhcp-client dhcp-client-program", "udhcpc" do |prog|
|
|
prefered_dhcp_client = prog
|
|
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 NotSetup
|
|
def to_s(io : IO)
|
|
io << "not setup"
|
|
end
|
|
end
|
|
|
|
|
|
|
|
class NetworkCommands
|
|
class_property cmd_network_configuration : IfconfigCommand.class | IPCommand.class = IfconfigCommand
|
|
class_property cmd_dhcp_client : UDHCPCCommand.class | NotSetup.class = NotSetup
|
|
|
|
class UDHCPCCommand
|
|
def self.run(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
|
|
|
|
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, "add", 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_string ]).success?
|
|
raise "(ifconfig) Cannot set ip address alias #{ip.to_string} for #{name}"
|
|
end
|
|
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
|
|
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
|
|
puts "no dhcp client: cannot perform dhcp on #{name}"
|
|
when UDHCPCCommand
|
|
cmd.run name
|
|
end
|
|
end
|
|
|
|
def self.set_alias(name : String, ip : IPAddress)
|
|
@@cmd_network_configuration.set_alias name, ip
|
|
end
|
|
end
|
|
|
|
|
|
#
|
|
# interface configuration
|
|
#
|
|
|
|
class InterfaceConfiguration
|
|
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
|
|
|
|
if @up
|
|
NetworkCommands.up @name
|
|
else
|
|
puts "not marked as 'up' -- ending here"
|
|
return
|
|
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
|
|
puts "no ipv4"
|
|
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
|
|
# TODO
|
|
#when Autoconfiguration
|
|
# NetworkCommands.autoconfiguration @name
|
|
#when DHCP
|
|
# NetworkCommands.dhcp6 @name
|
|
when NotSetup
|
|
puts "no ipv6"
|
|
else
|
|
raise "ipv4 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
|
|
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 = NotSetup.new
|
|
main_ip_v6 = 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
|
|
|
|
def which(cmd : String)
|
|
if Process.run("which", [ cmd ]).success?
|
|
puts "#{cmd} installed"
|
|
true
|
|
else
|
|
puts "#{cmd} not installed"
|
|
false
|
|
end
|
|
end
|
|
|
|
|
|
#
|
|
# 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
|
|
}
|
|
|
|
|
|
key = prefered_network_configuration_program
|
|
key = possible_network_configuration_cmds.keys.find { |key| 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 = prefered_dhcp_client
|
|
key = possible_dhcp_clients.keys.find { |key| which(key) } if key.nil?
|
|
# should not crash if there is no
|
|
NetworkCommands.cmd_dhcp_client = possible_dhcp_clients[key] unless key.nil?
|
|
|
|
|
|
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
|