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 { /// /// Represents the network information. /// public class NetworkSettings : SingletonBase { private const String EssidTag = "ESSID:"; /// /// Gets the local machine Host Name. /// public String HostName => Network.HostName; /// /// Retrieves the wireless networks. /// /// The adapter. /// A list of WiFi networks. public Task> RetrieveWirelessNetworks(String adapter) => this.RetrieveWirelessNetworks(new[] { adapter }); /// /// Retrieves the wireless networks. /// /// The adapters. /// A list of WiFi networks. public async Task> RetrieveWirelessNetworks(String[] adapters = null) { List result = new List(); 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(); } /// /// Setups the wireless network. /// /// Name of the adapter. /// The network ssid. /// The password (8 characters as minimum length). /// The 2-letter country code in uppercase. Default is US. /// True if successful. Otherwise, false. public async Task 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; } /// /// Retrieves the network adapters. /// /// A list of network adapters. public async Task> RetrieveAdapters() { const String hWaddr = "HWaddr "; const String ether = "ether "; List result = new List(); 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(); } /// /// Retrieves the current network adapter. /// /// The name of the current network adapter. public static async Task 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); } /// /// Retrieves current wireless connected network name. /// /// The connected network name. public Task 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(); } } }