Skip to content

Commit

Permalink
Fix API and asynchronous resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
joel0 committed Dec 13, 2020
1 parent 7362cde commit a4196f3
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 137 deletions.
74 changes: 49 additions & 25 deletions HostnamePlus/Models/IndexModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class IndexModel
/// A string of the client's User Agent from the request headers. Empty
/// string if the header is missing.
/// </summary>
public String UserAgent;
private String UserAgent;
/// <summary>
/// The IpInfoModel with the client's IP address.
/// </summary>
Expand All @@ -28,7 +28,7 @@ public class IndexModel
/// unsanitized user input, which may not be valid IP addresses or
/// truthful information.
/// </summary>
public readonly IpInfoModel[] ProxiedIpsInfo;
private IpInfoModel[] ProxiedIpsInfo = null;

/// <summary>
/// Constructs the model with the client's request connection.
Expand All @@ -47,31 +47,25 @@ public IndexModel(HttpRequest Request)
/// If the client is connected via IPv4, this URL is for the IPv6 only
/// API.
/// </summary>
public String OtherIpAPIURL {
get {
// If the connection is IPv6, hit the IPv4 API, and vice versa.
String subdomain = RemoteIpInfo.IsIPv6 ? "ipv4" : "ipv6";
return String.Format("//{0}.{1}/api/OtherIp",
subdomain, Program.BASE_URL);
}
public String MakeOtherIpAPIURL() {
// If the connection is IPv6, hit the IPv4 API, and vice versa.
String subdomain = RemoteIpInfo.IsIPv6 ? "ipv4" : "ipv6";
return String.Format("//{0}.{1}/api/OtherIp",
subdomain, Program.BASE_URL);
}

/// <summary>
/// The friendly text for the alternate IP family.
/// </summary>
public String OtherIpType {
get {
return RemoteIpInfo.IsIPv6 ? "IPv4" : "IPv6";
}
public String GetOtherIpType() {
return RemoteIpInfo.IsIPv6 ? "IPv4" : "IPv6";
}

/// <summary>
/// The friendly text for the IP family.
/// </summary>
public String IpType {
get {
return RemoteIpInfo.IsIPv6 ? "IPv6" : "IPv4";
}
public String GetIpType() {
return RemoteIpInfo.IsIPv6 ? "IPv6" : "IPv4";
}

/// <summary>
Expand Down Expand Up @@ -101,20 +95,39 @@ private static IpInfoModel[] GetProxiedIpsInfo(string xForwardedFor, int limit)
}

/// <summary>
/// Whether there's any IPs found in X-Forwarded-For header(s).
/// Start reverse DNS resolution of all X-Forwarded-For IP addresses in
/// the background. The results are available through
/// GetProxiedIpsInfo().
/// </summary>
public Boolean HasProxiedIps {
get {
return ProxiedIpsInfo.Length > 0;
public void StartProxyIpsHostNameResolution() {
foreach (IpInfoModel ipInfo in ProxiedIpsInfo) {
ipInfo.StartHostNameResolution();
}
}

/// <summary>
/// The reverse DNS of the client's IP, or N/A if the DNS lookup fails.
/// IPv6 reverse DNS lookups often fail.
/// Whether there's any IPs found in X-Forwarded-For header(s).
/// </summary>
public async Task<String> GetHostNameAsync() {
return await RemoteIpInfo.HostNameTask;
public Boolean HasProxiedIps() {
return ProxiedIpsInfo.Length > 0;
}

/// <summary>
/// If the X-Forwarded-For header exists, this is an array populated
/// with each IP found in the forward chain. Note that this is
/// unsanitized user input, which may not be valid IP addresses or
/// truthful information.
/// </summary>
public IpInfoModel[] GetProxiedIpsInfo() {
return ProxiedIpsInfo;
}

/// <summary>
/// A string of the client's User Agent from the request headers. Empty
/// string if the header is missing.
/// </summary>
public String GetUserAgent() {
return UserAgent;
}

/// <summary>
Expand All @@ -125,5 +138,16 @@ public String IP {
return RemoteIpInfo.IpString;
}
}

/// <summary>
/// The reverse DNS of the client's IP, or N/A if the DNS lookup fails.
/// IPv6 reverse DNS lookups often fail. Note that this is a blocking
/// call that waits on DNS resolution.
/// <summary>
public String HostName {
get {
return RemoteIpInfo.GetHostName();
}
}
}
}
226 changes: 127 additions & 99 deletions HostnamePlus/Models/IpInfoModel.cs
Original file line number Diff line number Diff line change
@@ -1,100 +1,128 @@
using System;
using System.Net;
using System.Threading.Tasks;

namespace HostnamePlus.Models
{
/// <summary>
/// A model for working with IP addresses and resolving information about
/// them.
/// </summary>
public class IpInfoModel
{
/// <summary>
/// The parsed IP address, if it's a valid IP. Otherwise null.
/// </summary>
private IPAddress Ip = null;
/// <summary>
/// The provided IP address string, if constructed with a string.
/// Null if constructed with an already parsed IP.
/// </summary>
private string RawIp = null;
/// <summary>
/// The async task that's resolving of the hostname.
/// "-" if the IP is malformed.
/// "N/A" if DNS resolution fails for any reason (usually NXDOMAIN).
/// Otherwise, contains the FQDN string.
/// </summary>
public readonly Task<String> HostNameTask;

/// <summary>
/// Constructs a model for requesting info about the provided IP.
/// Starts DNS resolution asynchronously.
/// </summary>
public IpInfoModel(IPAddress Ip) {
this.Ip = Ip;
HostNameTask = ResolveHostNameAsync();
}

/// <summary>
/// Constructs a model for requesting info about the provided IP.
/// Starts DNS resolution asynchronously, if the provided IP is valid.
/// </summary>
/// <param name="Ip">the IP address as a string, which is allowed to be
/// malformed. Malformed IPs will provide limited info.</param>
public IpInfoModel(string Ip) {
this.RawIp = Ip;
IPAddress.TryParse(Ip, out this.Ip);
HostNameTask = ResolveHostNameAsync();
}

/// <summary>
/// Attempts DNS resolution with the system's default DNS resolver.
/// </summary>
/// <returns>
/// A task doing resolution.
/// "-" if the IP is malformed.
/// "N/A" if the resolution fails.
/// Otherwise, the FQDN from DNS.
/// </returns>
private Task<String> ResolveHostNameAsync() {
return Task.Run(() => {
if (Ip != null) {
try {
return Dns.GetHostEntry(Ip).HostName;
} catch {
return "N/A";
}
} else {
return "-";
}
});
}

/// <summary>
/// The IP address as a string. If the IP provided to the constructor
/// was malformed, this will return a malformed IP.
/// </summary>
public String IpString {
get {
if (Ip != null) {
return Ip.ToString();
}
return RawIp;
}
}

/// <summary>
/// Checks whether the IP address is an IPv6 address or IPv4. Malformed
/// IPs result in a "false" (IPv4) return value.
/// </summary>
public Boolean IsIPv6 {
get {
if (Ip == null) {
return false;
}
return Ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
}
}
}
using System;
using System.Net;
using System.Threading.Tasks;

namespace HostnamePlus.Models
{
/// <summary>
/// A model for working with IP addresses and resolving information about
/// them.
/// </summary>
public class IpInfoModel
{
/// <summary>
/// The parsed IP address, if it's a valid IP. Otherwise null.
/// </summary>
private IPAddress Ip = null;
/// <summary>
/// The provided IP address string, if constructed with a string.
/// Null if constructed with an already parsed IP.
/// </summary>
private string RawIp = null;
/// <summary>
/// The async task that's resolving of the hostname.
/// "-" if the IP is malformed.
/// "N/A" if DNS resolution fails for any reason (usually NXDOMAIN).
/// Otherwise, contains the FQDN string.
/// </summary>
private Task<String> HostNameTask = null;

/// <summary>
/// Constructs a model for requesting info about the provided IP.
/// </summary>
public IpInfoModel(IPAddress Ip) {
this.Ip = Ip;
}

/// <summary>
/// Constructs a model for requesting info about the provided IP.
/// </summary>
/// <param name="Ip">the IP address as a string, which is allowed to be
/// malformed. Malformed IPs will provide limited info.</param>
public IpInfoModel(string Ip) {
this.RawIp = Ip;
IPAddress.TryParse(Ip, out this.Ip);
}

/// <summary>
/// Asynchronously start DNS resolution of the IP, if it hasn't been
/// started yet.
/// </summary>
public void StartHostNameResolution() {
if (HostNameTask == null) {
HostNameTask = ResolveHostNameAsync();
}
}

/// <summary>
/// Attempts DNS resolution with the system's default DNS resolver.
/// </summary>
/// <returns>
/// A task doing resolution.
/// "-" if the IP is malformed.
/// "N/A" if the resolution fails.
/// Otherwise, the FQDN from DNS.
/// </returns>
private Task<String> ResolveHostNameAsync() {
return Task.Run(() => {
if (Ip != null) {
try {
return Dns.GetHostEntry(Ip).HostName;
} catch {
return "N/A";
}
} else {
return "-";
}
});
}

/// <summary>
/// Query reverse DNS for the host name. Resolution may be slow.
/// </summary>
/// <returns>"-" if the IP is malformed. "N/A" if the resolution fails.
/// Otherwise, the FQDN returned by DNS.</returns>
public async Task<String> GetHostNameAsync() {
StartHostNameResolution();
return await HostNameTask;
}

/// <summary>
/// Query reverse DNS for the host name. This synchronous function
/// blocks until resolution completes.
/// <summary>
/// <returns>"-" if the IP is malformed. "N/A" if the resolution fails.
/// Otherwise, the FQDN returned by DNS.</returns>
public String GetHostName() {
StartHostNameResolution();
HostNameTask.Wait();
return HostNameTask.Result;
}

/// <summary>
/// The IP address as a string. If the IP provided to the constructor
/// was malformed, this will return a malformed IP.
/// </summary>
public String IpString {
get {
if (Ip != null) {
return Ip.ToString();
}
return RawIp;
}
}

/// <summary>
/// Checks whether the IP address is an IPv6 address or IPv4. Malformed
/// IPs result in a "false" (IPv4) return value.
/// </summary>
public Boolean IsIPv6 {
get {
if (Ip == null) {
return false;
}
return Ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
}
}
}
}
11 changes: 6 additions & 5 deletions HostnamePlus/Views/Index/Curl.cshtml
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
@using HostnamePlus.Models
@model HostnamePlus.Models.IndexModel
@{
String formatString = Model.HasProxiedIps ? "{0,-20}" : "{0,-11}";
Model.StartProxyIpsHostNameResolution();
String formatString = Model.HasProxiedIps() ? "{0,-20}" : "{0,-11}";
}
@String.Format(formatString, "Hostname: ")@await Model.GetHostNameAsync()
@String.Format(formatString, "Hostname: ")@Model.HostName
@String.Format(formatString, "IP: ")@Model.IP
@foreach (IpInfoModel ipInfo in Model.ProxiedIpsInfo)
@foreach (IpInfoModel ipInfo in Model.GetProxiedIpsInfo())
{
@String.Format(formatString, "X-Forwarder-For IP: ")@[email protected]("\n")
@String.Format(formatString, "X-Forwarded-For HN: ")@await ipInfo.HostNameTask@Html.Raw("\n")
@String.Format(formatString, "X-Forwarded-For HN: ")@await ipInfo.GetHostNameAsync()@Html.Raw("\n")
}
@String.Format(formatString, "UserAgent: ")@Model.UserAgent
@String.Format(formatString, "UserAgent: ")@Model.GetUserAgent()

Simplified output for curl. Change your UserAgent for HTML output.
Loading

0 comments on commit a4196f3

Please sign in to comment.