// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Net; using System.Data; using System.Net.Sockets; namespace Spludlow.Net { public class IpWhoIs { public static DataTable RecordTable; // They are all 000/8 private static Dictionary WhoIsServerLookup = new Dictionary(); private static Dictionary RdapServerLookup = new Dictionary(); static IpWhoIs() { RecordTable = IPv4AddressSpace(); foreach (DataRow row in RecordTable.Rows) { byte address = Byte.Parse(((string)row["prefix"]).Substring(0, 3)); if (row.IsNull("whois") == false) { string server = (string)row["whois"]; if (server.Length > 0) { WhoIsServerLookup.Add(address, server); } } if (row.IsNull("rdap") == false) { string rdap = (string)row["rdap"]; if (rdap.Length > 0) { int index = rdap.IndexOf(","); if (index != -1) rdap = rdap.Substring(0, index); RdapServerLookup.Add(address, rdap); } } } } public static DataTable IPv4AddressSpace() { string filename = Spludlow.Config.ProgramData + @"\Data\ipv4-address-space.xml"; if (File.Exists(filename) == false || (DateTime.Now - File.GetLastWriteTime(filename)) > TimeSpan.FromDays(7)) { string url = @"http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml"; string data = null; try { data = Spludlow.Net.Http.GetText(url, Encoding.UTF8); } catch (System.Net.WebException ee) { Spludlow.Log.Warning("IpWhoIs; Download Address space file failed", ee); } if (data != null) File.WriteAllText(filename, data, Encoding.UTF8); } if (File.Exists(filename) == false) throw new ApplicationException("IpWhoIs; Address Space File not found: " + filename); DataSet dataSet = Spludlow.Data.XML.ConvertRelational(filename, "record"); DataTable recordTable = dataSet.Tables["record"]; // Put the rdap data in the main table recordTable.Columns.Add("rdap", typeof(string)); foreach (DataRow recordRow in recordTable.Rows) { int record_Id = (int)recordRow["record_Id"]; StringBuilder servers = new StringBuilder(); foreach (DataRow rdapRow in dataSet.Tables["rdap"].Select("record_Id = " + record_Id)) { int rdap_Id = (int)rdapRow["rdap_Id"]; foreach (DataRow serverRow in dataSet.Tables["server"].Select("rdap_Id = " + rdap_Id)) { string server = (string)serverRow["serverValue"]; if (servers.Length > 0) servers.Append(", "); servers.Append(server); } } recordRow["rdap"] = servers.ToString(); } recordTable.DataSet.Tables.Remove(recordTable); return recordTable; } public static string WhoIsServer(string ipAddress) { IPAddress address = IPAddress.Parse(ipAddress); return WhoIsServer(address); } public static string WhoIsServer(IPAddress ipAddress) { byte prefix = ipAddress.GetAddressBytes()[0]; return WhoIsServer(prefix); } public static string WhoIsServer(byte prefix) { if (WhoIsServerLookup.ContainsKey(prefix) == false) return null; return WhoIsServerLookup[prefix]; } public static string RdapServer(string ipAddress) { IPAddress address = IPAddress.Parse(ipAddress); return RdapServer(address); } public static string RdapServer(IPAddress ipAddress) { byte prefix = ipAddress.GetAddressBytes()[0]; if (RdapServerLookup.ContainsKey(prefix) == false) return null; return RdapServerLookup[prefix]; } public static string WhoIs(string ipAddress) { string cacheDirectory = Spludlow.Config.ProgramData + @"\Data\IPWhoIs"; if (Directory.Exists(cacheDirectory) == false) Directory.CreateDirectory(cacheDirectory); string filename = cacheDirectory + @"\" + ipAddress + ".txt"; if (File.Exists(filename) == true && (DateTime.Now - File.GetLastWriteTime(filename)) < TimeSpan.FromDays(6 * 28)) return File.ReadAllText(filename, Encoding.ASCII); string whoIsServer = WhoIsServer(ipAddress); if (whoIsServer == null) return null; string text = WhoIsCall(ipAddress, whoIsServer, Environment.NewLine); bool found = false; using (StringReader reader = new StringReader(text)) { string line; while ((line = reader.ReadLine()) != null) { line = line.Trim(); if (line.Length > 0 && line[0] != '%') { found = true; break; } } } if (found == false) { Spludlow.Log.Error("Who is: " + ipAddress, text); throw new ApplicationException("Whois contained no data in body: " + ipAddress); } File.WriteAllText(filename, text, Encoding.ASCII); return text; } public static string WhoIsCall(string ipAddress, string whoIsServer, string resultEndOfLine) { string sendText = ipAddress + System.Environment.NewLine; if (whoIsServer == "whois.arin.net") sendText = "+ n " + sendText; byte[] sendData = System.Text.Encoding.ASCII.GetBytes(sendText.ToCharArray()); using (TcpClient tcpClient = new TcpClient()) { tcpClient.Connect(whoIsServer, 43); Stream stream = tcpClient.GetStream(); stream.Write(sendData, 0, sendData.Length); using (StreamReader reader = new StreamReader(stream, System.Text.Encoding.ASCII)) { StringBuilder text = new StringBuilder(); string line = null; while ((line = reader.ReadLine()) != null) { text.Append(line); text.Append(resultEndOfLine); } return text.ToString(); } } } public static string WhoIsCallRdap(string serverUrl, string ip) // Not working with some registers { StringBuilder url = new StringBuilder(); url.Append(serverUrl); if (serverUrl.EndsWith("/") == false) url.Append("/"); url.Append("ip/"); url.Append(ip); try { using (WebClient client = new WebClient()) { using (Stream stream = client.OpenRead(url.ToString())) { using (StreamReader reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } } } catch (System.Net.WebException ee) { HttpWebResponse response = ee.Response as HttpWebResponse; if (response != null && response.StatusCode == HttpStatusCode.NotFound) throw new ApplicationException("RDAP: Not found (404): " + ip, ee); throw ee; } } public static string WhoIsCallRest(string serverUrl, string ip, string acceptFormat) { // https://github.com/RIPE-NCC/whois/wiki/WHOIS-REST-API-search StringBuilder url = new StringBuilder(); url.Append(serverUrl); if (serverUrl.EndsWith("/") == false) url.Append("/"); url.Append("search?query-string="); url.Append(ip); using (WebClient client = new WebClient()) { client.Headers.Add(HttpRequestHeader.Accept, "application/" + acceptFormat); using (Stream stream = client.OpenRead(url.ToString())) { using (StreamReader reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } } } public static DataTable ReportCacheDirectory() { DataTable table = new DataTable(); table.Columns.Add("SortIP", typeof(string)); table.Columns.Add("Filename", typeof(string)); string cacheDirectory = Spludlow.Config.ProgramData + @"\Data\IPWhoIs"; List badList = new List(); foreach (string filename in Directory.GetFiles(cacheDirectory, "*.txt")) { string whoIsText = File.ReadAllText(filename, Encoding.ASCII); Dictionary> info = Parse(whoIsText); //throw new ApplicationException(info.Keys.Count.ToString()); string name = Path.GetFileName(filename); if (info.Keys.Count == 0) { badList.Add(name); continue; } DataRow row = table.NewRow(); string[] parts = Path.GetFileNameWithoutExtension(filename).Split(new char[] { '.' }); StringBuilder sortIP = new StringBuilder(); foreach (string part in parts) { if (sortIP.Length > 0) sortIP.Append("."); sortIP.Append(Int32.Parse(part).ToString("000")); } row["SortIP"] = sortIP.ToString(); row["Filename"] = name; foreach (string columnName in info.Keys) { if (table.Columns.Contains(columnName) == false) table.Columns.Add(columnName, typeof(string)); List items = info[columnName]; if (items.Count == 1) { row[columnName] = items[0]; } else { StringBuilder text = new StringBuilder(); foreach (string item in items) { if (text.Length > 0) text.Append(", "); text.Append(item); } row[columnName] = text.ToString(); } } table.Rows.Add(row); //break; } if (badList.Count > 0) Spludlow.Log.Warning("ReportCacheDirectory; Bad file list", badList.ToArray()); return table; } public static Dictionary> Parse(string whoIsText) { Dictionary> result = new Dictionary>(); // "Country", "Customer", "NetName" }; using (StringReader reader = new StringReader(whoIsText)) { string lastKey = null; string line = null; while ((line = reader.ReadLine()) != null) { line = line.Trim(); if (line.Length == 0 || line[0] == '%' || line[0] == '#') continue; string key; int index = line.IndexOf(':'); if (index == -1) key = lastKey; else key = line.Substring(0, index).Trim(); if (key == null) continue; // throw new ApplicationException("IP Who Is parse bad: " + line); // continue; key = key.ToLower(); // "whois.arin.net" fixes //if (key == "Country") // key = "country"; //if (key == "NetName") // key = "netname"; if (key == "organization") // Customer key = "descr"; if (key == "netrange") key = "inetnum"; string data = ""; if (index == -1) { data = line.Trim(); } else { if (line.Length > index + 1) data = line.Substring(index + 1).Trim(); } if (data.Length == 0) continue; if (result.ContainsKey(key) == false) result.Add(key, new List()); result[key].Add(data); lastKey = key; } } return result; } public static string CountryCode(string ip) // seen some br (brazil) not working { string cacheKey = "Spludlow.Net.IpWhoIs.CountryCode"; Dictionary lookup = (Dictionary)Spludlow.Caching.Get(cacheKey); if (lookup == null) lookup = new Dictionary(); if (lookup.ContainsKey(ip) == true) return lookup[ip]; string result = ""; string whoisText = Spludlow.Net.IpWhoIs.WhoIs(ip); if (whoisText != null) { Dictionary> info = Spludlow.Net.IpWhoIs.Parse(whoisText); if (info.ContainsKey("country") == true && info["country"].Count > 0) result = info["country"][0]; } lookup.Add(ip, result); Spludlow.Caching.Set(cacheKey, lookup); return result; } } }