Skip to content

Instantly share code, notes, and snippets.

@badeend
Created December 23, 2021 12:20
Show Gist options
  • Save badeend/b5740b0d86388422e838d4f76cf9aa70 to your computer and use it in GitHub Desktop.
Save badeend/b5740b0d86388422e838d4f76cf9aa70 to your computer and use it in GitHub Desktop.
Domainname based firewall for WASI Sockets
using System.Net;
DemoWasiHost.main();
// EXAMPLE:
class DemoWasiHost
{
public static void main()
{
// Set up networking capabilities:
var network = new NetworkingCapability(
new FirewallRule[]
{
new("webassembly.org", 443),
new("smtp.example.com", 25),
}
);
// Run demo module:
DemoUntrustedWasiModule.main(network);
}
}
class DemoUntrustedWasiModule
{
public static void main(NetworkingCapability network)
{
// Example 1:
var ip1 = network.resolve_host("webassembly.org");
var sock1 = network.open_socket();
sock1.connect(ip1, 443);
// Example 2: (fails)
// var ip2 = IPAddress.Parse("104.21.85.250"); // Hardcoded IP of: webassembly.org
// var sock2 = network.open_socket();
// sock2.connect(ip2, 443); // Error: notcapable
// Example 3: (fails)
// var ip3 = network.resolve_host("evil.com"); // Error: notcapable
Console.WriteLine("Done");
}
}
// IMPLEMENTATION:
public record FirewallRule(string Host, int Port);
public class NetworkingCapability
{
internal IReadOnlyList<FirewallRule> FirewallRules { get; }
internal Dictionary<IPAddress, HashSet<string>> AssociatedHostNamesByIp { get; } = new();
internal NetworkingCapability(FirewallRule[] firewallRules)
{
this.FirewallRules = firewallRules;
}
public IPAddress resolve_host(string hostName /* ... */)
{
// Validate:
if (this.FirewallRules.Where(rule => rule.Host == hostName).Count() == 0)
{
throw new Exception("notcapable: Capabilities insufficient.");
}
// Perform actual lookup:
var ipAddress = Dns.GetHostEntry(hostName).AddressList.First();
// Store association:
this.AssociatedHostNamesByIp.GetOrAdd(ipAddress, _ => new()).Add(hostName);
return ipAddress;
}
public Socket open_socket(/* ... */)
{
return new Socket(this);
}
}
public class Socket
{
internal NetworkingCapability NetworkingCapability { get; }
internal Socket(NetworkingCapability networkingCapability)
{
this.NetworkingCapability = networkingCapability;
}
public void connect(IPAddress host, int port)
{
// Get known associated domain names with this IP:
var associatedHostNames = this.NetworkingCapability.AssociatedHostNamesByIp.GetValueOrDefault(host, new());
if (associatedHostNames.Count == 0)
{
throw new Exception("notcapable: Capabilities insufficient.");
}
// Check to see if any firewall rules allow this connection:
var matchingRules = this.NetworkingCapability.FirewallRules.Where(rule => associatedHostNames.Contains(rule.Host) && rule.Port == port);
if (matchingRules.Count() == 0)
{
throw new Exception("notcapable: Capabilities insufficient.");
}
// Perform actual connection:
// NOT IMPLEMENTED
}
}
public static class Utils
{
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> valueFactory)
{
if (dictionary.TryGetValue(key, out var existingValue))
{
return existingValue;
}
var newValue = valueFactory(key);
dictionary.Add(key, newValue);
return newValue;
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment