RaspberryIO_26/Unosquare.RaspberryIO/Computer/NetworkSettings.cs
2019-12-06 23:12:34 +01:00

272 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Swan;
using Swan.Logging;
using Swan.Net;
namespace Unosquare.RaspberryIO.Computer {
/// <summary>
/// Represents the network information.
/// </summary>
public class NetworkSettings : SingletonBase<NetworkSettings> {
private const String EssidTag = "ESSID:";
/// <summary>
/// Gets the local machine Host Name.
/// </summary>
public String HostName => Network.HostName;
/// <summary>
/// Retrieves the wireless networks.
/// </summary>
/// <param name="adapter">The adapter.</param>
/// <returns>A list of WiFi networks.</returns>
public Task<List<WirelessNetworkInfo>> RetrieveWirelessNetworks(String adapter) => this.RetrieveWirelessNetworks(new[] { adapter });
/// <summary>
/// Retrieves the wireless networks.
/// </summary>
/// <param name="adapters">The adapters.</param>
/// <returns>A list of WiFi networks.</returns>
public async Task<List<WirelessNetworkInfo>> RetrieveWirelessNetworks(String[]? adapters = null) {
List<WirelessNetworkInfo> result = new List<WirelessNetworkInfo>();
foreach(String? networkAdapter in adapters ?? (await this.RetrieveAdapters()).Where(x => x.IsWireless).Select(x => x.Name)) {
String wirelessOutput = await ProcessRunner.GetProcessOutputAsync("iwlist", $"{networkAdapter} scanning").ConfigureAwait(false);
String[] outputLines = wirelessOutput.Split('\n').Select(x => x.Trim()).Where(x => String.IsNullOrWhiteSpace(x) == false).ToArray();
for(Int32 i = 0; i < outputLines.Length; i++) {
String line = outputLines[i];
if(line.StartsWith(EssidTag) == false) {
continue;
}
WirelessNetworkInfo network = new WirelessNetworkInfo {
Name = line.Replace(EssidTag, String.Empty).Replace("\"", String.Empty)
};
while(true) {
if(i + 1 >= outputLines.Length) {
break;
}
// should look for two lines before the ESSID acording to the scan
line = outputLines[i - 2];
if(!line.StartsWith("Quality=")) {
continue;
}
network.Quality = line.Replace("Quality=", String.Empty);
break;
}
while(true) {
if(i + 1 >= outputLines.Length) {
break;
}
// should look for a line before the ESSID acording to the scan
line = outputLines[i - 1];
if(!line.StartsWith("Encryption key:")) {
continue;
}
network.IsEncrypted = line.Replace("Encryption key:", String.Empty).Trim() == "on";
break;
}
if(result.Any(x => x.Name == network.Name) == false) {
result.Add(network);
}
}
}
return result
.OrderBy(x => x.Name)
.ToList();
}
/// <summary>
/// Setups the wireless network.
/// </summary>
/// <param name="adapterName">Name of the adapter.</param>
/// <param name="networkSsid">The network ssid.</param>
/// <param name="password">The password (8 characters as minimum length).</param>
/// <param name="countryCode">The 2-letter country code in uppercase. Default is US.</param>
/// <returns>True if successful. Otherwise, false.</returns>
public async Task<Boolean> SetupWirelessNetwork(String adapterName, String networkSsid, String? password = null, String countryCode = "US") {
// TODO: Get the country where the device is located to set 'country' param in payload var
String payload = $"country={countryCode}\nctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\n";
if(!String.IsNullOrWhiteSpace(password) && password.Length < 8) {
throw new InvalidOperationException("The password must be at least 8 characters length.");
}
payload += String.IsNullOrEmpty(password)
? $"network={{\n\tssid=\"{networkSsid}\"\n\tkey_mgmt=NONE\n\t}}\n"
: $"network={{\n\tssid=\"{networkSsid}\"\n\tpsk=\"{password}\"\n\t}}\n";
try {
File.WriteAllText("/etc/wpa_supplicant/wpa_supplicant.conf", payload);
_ = await ProcessRunner.GetProcessOutputAsync("pkill", "-f wpa_supplicant");
_ = await ProcessRunner.GetProcessOutputAsync("ifdown", adapterName);
_ = await ProcessRunner.GetProcessOutputAsync("ifup", adapterName);
} catch(Exception ex) {
ex.Log(nameof(NetworkSettings));
return false;
}
return true;
}
/// <summary>
/// Retrieves the network adapters.
/// </summary>
/// <returns>A list of network adapters.</returns>
public async Task<List<NetworkAdapterInfo>> RetrieveAdapters() {
const String hWaddr = "HWaddr ";
const String ether = "ether ";
List<NetworkAdapterInfo> result = new List<NetworkAdapterInfo>();
String interfacesOutput = await ProcessRunner.GetProcessOutputAsync("ifconfig");
String[] wlanOutput = (await ProcessRunner.GetProcessOutputAsync("iwconfig")).Split('\n').Where(x => x.Contains("no wireless extensions.") == false).ToArray();
String[] outputLines = interfacesOutput.Split('\n').Where(x => String.IsNullOrWhiteSpace(x) == false).ToArray();
for(Int32 i = 0; i < outputLines.Length; i++) {
// grab the current line
String line = outputLines[i];
// skip if the line is indented
if(Char.IsLetterOrDigit(line[0]) == false) {
continue;
}
// Read the line as an adapter
NetworkAdapterInfo adapter = new NetworkAdapterInfo {
Name = line.Substring(0, line.IndexOf(' ')).TrimEnd(':')
};
// Parse the MAC address in old version of ifconfig; it comes in the first line
if(line.IndexOf(hWaddr, StringComparison.Ordinal) >= 0) {
Int32 startIndexHwd = line.IndexOf(hWaddr, StringComparison.Ordinal) + hWaddr.Length;
adapter.MacAddress = line.Substring(startIndexHwd, 17).Trim();
}
// Parse the info in lines other than the first
for(Int32 j = i + 1; j < outputLines.Length; j++) {
// Get the contents of the indented line
String indentedLine = outputLines[j];
// We have hit the next adapter info
if(Char.IsLetterOrDigit(indentedLine[0])) {
i = j - 1;
break;
}
// Parse the MAC address in new versions of ifconfig; it no longer comes in the first line
if(indentedLine.IndexOf(ether, StringComparison.Ordinal) >= 0 && String.IsNullOrWhiteSpace(adapter.MacAddress)) {
Int32 startIndexHwd = indentedLine.IndexOf(ether, StringComparison.Ordinal) + ether.Length;
adapter.MacAddress = indentedLine.Substring(startIndexHwd, 17).Trim();
}
// Parse the IPv4 Address
GetIPv4(indentedLine, adapter);
// Parse the IPv6 Address
GetIPv6(indentedLine, adapter);
// we have hit the end of the output in an indented line
if(j >= outputLines.Length - 1) {
i = outputLines.Length;
}
}
// Retrieve the wireless LAN info
String wlanInfo = wlanOutput.FirstOrDefault(x => x.StartsWith(adapter.Name));
if(wlanInfo != null) {
adapter.IsWireless = true;
String[] essidParts = wlanInfo.Split(new[] { EssidTag }, StringSplitOptions.RemoveEmptyEntries);
if(essidParts.Length >= 2) {
adapter.AccessPointName = essidParts[1].Replace("\"", String.Empty).Trim();
}
}
// Add the current adapter to the result
result.Add(adapter);
}
return result.OrderBy(x => x.Name).ToList();
}
/// <summary>
/// Retrieves the current network adapter.
/// </summary>
/// <returns>The name of the current network adapter.</returns>
public static async Task<String?> GetCurrentAdapterName() {
String result = await ProcessRunner.GetProcessOutputAsync("route").ConfigureAwait(false);
String defaultLine = result.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(l => l.StartsWith("default", StringComparison.OrdinalIgnoreCase));
return defaultLine?.Trim().Substring(defaultLine.LastIndexOf(" ", StringComparison.OrdinalIgnoreCase) + 1);
}
/// <summary>
/// Retrieves current wireless connected network name.
/// </summary>
/// <returns>The connected network name.</returns>
public Task<String> GetWirelessNetworkName() => ProcessRunner.GetProcessOutputAsync("iwgetid", "-r");
private static void GetIPv4(String indentedLine, NetworkAdapterInfo adapter) {
String? addressText = ParseOutputTagFromLine(indentedLine, "inet addr:") ?? ParseOutputTagFromLine(indentedLine, "inet ");
if(addressText == null) {
return;
}
if(IPAddress.TryParse(addressText, out IPAddress outValue)) {
adapter.IPv4 = outValue;
}
}
private static void GetIPv6(String indentedLine, NetworkAdapterInfo adapter) {
String? addressText = ParseOutputTagFromLine(indentedLine, "inet6 addr:") ?? ParseOutputTagFromLine(indentedLine, "inet6 ");
if(addressText == null) {
return;
}
if(IPAddress.TryParse(addressText, out IPAddress outValue)) {
adapter.IPv6 = outValue;
}
}
private static String? ParseOutputTagFromLine(String indentedLine, String tagName) {
if(indentedLine.IndexOf(tagName, StringComparison.Ordinal) < 0) {
return null;
}
Int32 startIndex = indentedLine.IndexOf(tagName, StringComparison.Ordinal) + tagName.Length;
StringBuilder builder = new StringBuilder(1024);
for(Int32 c = startIndex; c < indentedLine.Length; c++) {
Char currentChar = indentedLine[c];
if(!Char.IsPunctuation(currentChar) && !Char.IsLetterOrDigit(currentChar)) {
break;
}
_ = builder.Append(currentChar);
}
return builder.ToString();
}
}
}