networkctl/src/network_commands.cr

396 lines
10 KiB
Crystal

# TODO: OpenBSD: no '-w' parameter for sysctl
# TODO: OpenBSD: test scanning for wireless AP with ifconfig
# 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_property cmd_sysctl : SysctlCommand.class | OpenBSDSysctlCommand.class | NotSetup.class = NotSetup
class DNS
property addresses : Array(String)
property search : Array(String)
def initialize()
@addresses = Array(String).new
@search = Array(String).new
end
def to_s(io : IO)
addresses.each do |ip|
io << "nameserver #{ip}"
end
io << "search #{search.join(" ")}" unless search.empty?
end
def execute
if Context.simulation
puts "simulation, writing in #{Context.root}/etc/resolv.conf:"
@addresses.each do |address|
puts "\tnameserver #{address}"
end
puts "\tsearch #{@search.join(" ")}" unless search.empty?
else
Dir.mkdir_p("#{Context.root}/etc/")
File.open("#{Context.root}/etc/resolv.conf", "w+") do |file|
@addresses.each do |address|
file.puts "nameserver #{address}"
end
file.puts "search #{@search.join(" ")}" unless search.empty?
end
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: (?<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 WPASupplicant
# store the AP passphrase in the wpa_supplicant way
def self.passphrase(ssid : String, passphrase : String)
dirpath = "#{Context.root}/#{Context.keydir}"
Dir.mkdir_p(dirpath) unless Dir.exists?(dirpath)
# verify if the passphrase already is stored for wpa_supplicant
return if File.exists?("#{dirpath}/#{ssid}.conf")
File.open("#{dirpath}/#{ssid}.conf", "w+") do |file|
Do.run("wpa_passphrase", [ ssid, "#{passphrase}" ]) do |p|
p.output.each_line do |line|
file.puts line
end
end
end
end
def self.connection(ifname : String, ssid : String)
dirpath = "#{Context.root}/#{Context.keydir}"
unless Do.run("wpa_supplicant",
[ "-B", "-c", "#{dirpath}/#{ssid}.conf", "-i", ifname ]).success?
raise "(wpa_supplicant) cannot connect to the wireless AP #{ssid} via the interface #{ifname}"
end
end
def self.disconnection(ifname : String, ssid : String)
# TODO: this kills every running wpa_supplicant application
# not only the instance for ifname
dirpath = "#{Context.root}/#{Context.keydir}"
unless Do.run("pkill", [ "wpa_supplicant" ]).success?
raise "(wpa_supplicant) cannot stop the wpa_supplicant daemon"
end
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 OpenBSDDHClientCommand < DHClientCommand
def self.dhcp6(ifname : String)
puts "TODO: dhcp6 on OpenBSD"
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) cannot get SSID with ifconfig on Linux"
end
end
class OpenBSDIfconfigCommand < IfconfigCommand
# get the available SSID
def self.scan(ifname : String) : Array(String)
ssids = Array(String).new
Do.run("ifconfig", [ ifname, "scan" ]) do |p|
p.output.each_line do |line|
ssid = /nwid (?<ssid>[a-zA-Z0-9_-]+)/.match(line).try &.["ssid"]
unless ssid.nil?
ssids << ssid
end
end
end
if ssids.empty?
raise "(openbsd ifconfig) cannot get ssid list from #{ifname}"
end
ssids
end
def self.autoconfiguration(ifname : String)
unless Do.run("ifconfig", [ ifname, "inet6", "autoconf" ]).success?
raise "(openbsd ifconfig) Cannot set IPv6 autoconfiguration on #{ifname}"
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
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}"
end
end
class SysctlCommand
def self.autoconfiguration(ifname : String)
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
end
class OpenBSDSysctlCommand
def self.autoconfiguration(ifname : String)
OpenBSDIfconfigCommand.autoconfiguration ifname
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)
cmd = @@cmd_sysctl
case cmd
when NotSetup.class
raise "cannot autoconfigure IPv6"
else
cmd.autoconfiguration ifname
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.store_access_point_keys(ssid : String, security : WirelessAPSetup::WPA)
# TODO: only one way to do it
NetworkCommands::WPASupplicant.passphrase ssid, security.key
end
def self.wireless_access_point_connection(ifname : String, ssid : String)
NetworkCommands::WPASupplicant.connection ifname, ssid
end
def self.wireless_access_point_disconnection(ifname : String, ssid : String)
NetworkCommands::WPASupplicant.disconnection ifname, ssid
end
end