feature: Bootstrapper secure server discovery
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Disco.Services.Interop.DNS
|
||||
{
|
||||
public class ADnsRecord : DnsRecord
|
||||
{
|
||||
public IPAddress Address { get; }
|
||||
|
||||
public ADnsRecord(string name, TimeSpan timeToLive, uint address)
|
||||
: base(name, DnsRecordType.A, timeToLive, UIntToIPAddress(address).ToString())
|
||||
{
|
||||
Address = UIntToIPAddress(address);
|
||||
}
|
||||
|
||||
private static IPAddress UIntToIPAddress(uint address)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(address);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(bytes);
|
||||
return new IPAddress(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Disco.Services.Interop.DNS
|
||||
{
|
||||
public class CnameDnsRecord : DnsRecord
|
||||
{
|
||||
public CnameDnsRecord(string name, TimeSpan timeToLive, string canonicalName)
|
||||
: base(name, DnsRecordType.Cname, timeToLive, canonicalName)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Disco.Services.Interop.DNS
|
||||
{
|
||||
public abstract class DnsRecord
|
||||
{
|
||||
public string Name { get; }
|
||||
public DnsRecordType Type { get; }
|
||||
public TimeSpan TimeToLive { get; }
|
||||
public string Content { get; }
|
||||
|
||||
protected DnsRecord(string name, DnsRecordType type, TimeSpan timeToLive, string content)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
TimeToLive = timeToLive;
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Disco.Services.Interop.DNS
|
||||
{
|
||||
public enum DnsRecordType
|
||||
{
|
||||
A = 0x01,
|
||||
Cname = 0x05,
|
||||
Txt = 0x10,
|
||||
Srv = 0x21
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Disco.Services.Interop.DNS
|
||||
{
|
||||
public class DnsService
|
||||
{
|
||||
public DnsService()
|
||||
{
|
||||
}
|
||||
|
||||
public static List<T> Query<T>(string name, bool bypassCache = false) where T : DnsRecord
|
||||
{
|
||||
DnsRecordType recordType;
|
||||
if (typeof(T) == typeof(ADnsRecord))
|
||||
recordType = DnsRecordType.A;
|
||||
else if (typeof(T) == typeof(CnameDnsRecord))
|
||||
recordType = DnsRecordType.Cname;
|
||||
else if (typeof(T) == typeof(TxtDnsRecord))
|
||||
recordType = DnsRecordType.Txt;
|
||||
else if (typeof(T) == typeof(SrvDnsRecord))
|
||||
recordType = DnsRecordType.Srv;
|
||||
else
|
||||
throw new NotSupportedException($"Unsupported DNS record type: {typeof(T).Name}");
|
||||
var records = NativeDns.QueryRecords(recordType, name, bypassCache);
|
||||
return records.ConvertAll(r => (T)r);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Disco.Services.Interop.DNS
|
||||
{
|
||||
internal static class NativeDns
|
||||
{
|
||||
|
||||
[DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
|
||||
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string pszName, NativeDnsQueryTypes wType, NativeDnsQueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);
|
||||
|
||||
[DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
|
||||
private const int DNS_ERROR_RCODE_NAME_ERROR = 0x232B;
|
||||
private const int DNS_ERROR_BAD_PACKET = 0x251E;
|
||||
|
||||
public static List<DnsRecord> QueryRecords(DnsRecordType type, string name, bool bypassCache)
|
||||
{
|
||||
NativeDnsQueryTypes queryType;
|
||||
Func<IntPtr, Tuple<DnsRecord, IntPtr>> marshaller;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case DnsRecordType.A:
|
||||
queryType = NativeDnsQueryTypes.DNS_TYPE_A;
|
||||
marshaller = MarshalARecord;
|
||||
break;
|
||||
case DnsRecordType.Cname:
|
||||
queryType = NativeDnsQueryTypes.DNS_TYPE_CNAME;
|
||||
marshaller = MarshalCnameRecord;
|
||||
break;
|
||||
case DnsRecordType.Txt:
|
||||
queryType = NativeDnsQueryTypes.DNS_TYPE_TEXT;
|
||||
marshaller = MarshalTxtRecord;
|
||||
break;
|
||||
case DnsRecordType.Srv:
|
||||
queryType = NativeDnsQueryTypes.DNS_TYPE_SRV;
|
||||
marshaller = MarshalSrvRecord;
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Unsupported DNS record type: {type}");
|
||||
}
|
||||
|
||||
IntPtr rrPointers = IntPtr.Zero;
|
||||
var records = new List<DnsRecord>();
|
||||
var retry = 5;
|
||||
retry:
|
||||
try
|
||||
{
|
||||
int queryResult = DnsQuery(ref name, queryType, bypassCache ? NativeDnsQueryOptions.DNS_QUERY_BYPASS_CACHE : NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref rrPointers, 0);
|
||||
if (queryResult != 0)
|
||||
{
|
||||
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
|
||||
return records;
|
||||
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
|
||||
{
|
||||
// Sometimes a BAD_PACKET error is returned, retry a few times
|
||||
Thread.Sleep(100);
|
||||
retry--;
|
||||
goto retry;
|
||||
}
|
||||
else
|
||||
throw new Win32Exception(queryResult);
|
||||
}
|
||||
for (var rrPointer = rrPointers; !rrPointer.Equals(IntPtr.Zero);)
|
||||
{
|
||||
var (record, rrPointerNext) = marshaller(rrPointer);
|
||||
records.Add(record);
|
||||
rrPointer = rrPointerNext;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rrPointers != IntPtr.Zero)
|
||||
DnsRecordListFree(rrPointers, 0);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
private static Tuple<DnsRecord, IntPtr> MarshalARecord(IntPtr pointer)
|
||||
{
|
||||
var native = Marshal.PtrToStructure<NativeDnsAData>(pointer);
|
||||
var record = new ADnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.IpAddress);
|
||||
return Tuple.Create((DnsRecord)record, native.pNext);
|
||||
}
|
||||
|
||||
private static Tuple<DnsRecord, IntPtr> MarshalCnameRecord(IntPtr pointer)
|
||||
{
|
||||
var native = Marshal.PtrToStructure<NativeDnsPtrData>(pointer);
|
||||
var record = new CnameDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pNameHost);
|
||||
return Tuple.Create((DnsRecord)record, native.pNext);
|
||||
}
|
||||
|
||||
private static Tuple<DnsRecord, IntPtr> MarshalTxtRecord(IntPtr pointer)
|
||||
{
|
||||
var native = Marshal.PtrToStructure<NativeDnsTxtData>(pointer);
|
||||
var record = new TxtDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pStringArray);
|
||||
return Tuple.Create((DnsRecord)record, native.pNext);
|
||||
}
|
||||
|
||||
private static Tuple<DnsRecord, IntPtr> MarshalSrvRecord(IntPtr pointer)
|
||||
{
|
||||
var native = Marshal.PtrToStructure<NativeDnsSrvData>(pointer);
|
||||
var record = new SrvDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pNameTarget, native.wPriority, native.wWeight, native.wPort);
|
||||
return Tuple.Create((DnsRecord)record, native.pNext);
|
||||
}
|
||||
|
||||
private enum NativeDnsQueryOptions
|
||||
{
|
||||
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
|
||||
DNS_QUERY_BYPASS_CACHE = 8,
|
||||
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
|
||||
DNS_QUERY_NO_HOSTS_FILE = 0x40,
|
||||
DNS_QUERY_NO_LOCAL_NAME = 0x20,
|
||||
DNS_QUERY_NO_NETBT = 0x80,
|
||||
DNS_QUERY_NO_RECURSION = 4,
|
||||
DNS_QUERY_NO_WIRE_QUERY = 0x10,
|
||||
DNS_QUERY_RESERVED = -16777216,
|
||||
DNS_QUERY_RETURN_MESSAGE = 0x200,
|
||||
DNS_QUERY_STANDARD = 0,
|
||||
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
|
||||
DNS_QUERY_USE_TCP_ONLY = 2,
|
||||
DNS_QUERY_WIRE_ONLY = 0x100
|
||||
}
|
||||
|
||||
private enum NativeDnsQueryTypes
|
||||
{
|
||||
DNS_TYPE_A = 0x0001,
|
||||
DNS_TYPE_CNAME = 0x0005,
|
||||
DNS_TYPE_TEXT = 0x0010,
|
||||
DNS_TYPE_SRV = 0x0021
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct NativeDnsSrvData
|
||||
{
|
||||
public IntPtr pNext;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string pName;
|
||||
public ushort wType;
|
||||
public ushort wDataLength;
|
||||
public int flags;
|
||||
public int dwTtl;
|
||||
public int dwReserved;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string pNameTarget;
|
||||
public ushort wPriority;
|
||||
public ushort wWeight;
|
||||
public ushort wPort;
|
||||
public ushort Pad;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct NativeDnsTxtData
|
||||
{
|
||||
public IntPtr pNext;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string pName;
|
||||
public ushort wType;
|
||||
public ushort wDataLength;
|
||||
public int flags;
|
||||
public int dwTtl;
|
||||
public int dwReserved;
|
||||
public uint dwStringLength;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string pStringArray;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct NativeDnsPtrData
|
||||
{
|
||||
public IntPtr pNext;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string pName;
|
||||
public ushort wType;
|
||||
public ushort wDataLength;
|
||||
public int flags;
|
||||
public int dwTtl;
|
||||
public int dwReserved;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string pNameHost;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct NativeDnsAData
|
||||
{
|
||||
public IntPtr pNext;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string pName;
|
||||
public ushort wType;
|
||||
public ushort wDataLength;
|
||||
public int flags;
|
||||
public int dwTtl;
|
||||
public int dwReserved;
|
||||
public uint IpAddress;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace Disco.Services.Interop.DNS
|
||||
{
|
||||
public class SrvDnsRecord : DnsRecord
|
||||
{
|
||||
public string Target { get; }
|
||||
public ushort Priority { get; }
|
||||
public ushort Weight { get; }
|
||||
public ushort Port { get; }
|
||||
|
||||
public SrvDnsRecord(string name, TimeSpan timeToLive, string target, ushort priority, ushort weight, ushort port)
|
||||
: base(name, DnsRecordType.Srv, timeToLive, $"{priority} {weight} {port} {target}")
|
||||
{
|
||||
Target = target;
|
||||
Priority = priority;
|
||||
Weight = weight;
|
||||
Port = port;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Disco.Services.Interop.DNS
|
||||
{
|
||||
public class TxtDnsRecord : DnsRecord
|
||||
{
|
||||
public TxtDnsRecord(string name, TimeSpan timeToLive, string text)
|
||||
: base(name, DnsRecordType.Txt, timeToLive, text)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Interop.DiscoServices;
|
||||
using Disco.Services.Devices.Enrolment;
|
||||
using Disco.Services.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
@@ -221,6 +222,10 @@ namespace Disco.Services.Interop.DiscoServices
|
||||
RepairerLogged = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerLoggedDate : j.RepairerLoggedDate,
|
||||
RepairerCompleted = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerCompletedDate : j.RepairerCompletedDate
|
||||
}).ToList();
|
||||
|
||||
m.Stat_EnrollmentDiscovery = WindowsDeviceEnrolment.GetDiscoveryMethodStatistics()
|
||||
.Where(s => s.Value != 0)
|
||||
.Select(s => new StatisticInt() { Key = s.Key.ToString(), Value = s.Value }).ToList();
|
||||
}
|
||||
|
||||
m.InstalledPlugins = Plugins.Plugins.GetPlugins().Select(manifest => new StatisticString() { Key = manifest.Id, Value = manifest.VersionFormatted }).ToList();
|
||||
|
||||
@@ -1,13 +1,51 @@
|
||||
using Disco.Services.Interop.DiscoServices;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Disco.Services.Interop.VicEduDept
|
||||
{
|
||||
public class VicSmart
|
||||
{
|
||||
public static bool IsVicSmartDeployment()
|
||||
{
|
||||
var nics = NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
|
||||
.ToList();
|
||||
|
||||
bool found10Net = false;
|
||||
foreach (var nic in nics)
|
||||
{
|
||||
if (nic.Supports(NetworkInterfaceComponent.IPv4))
|
||||
{
|
||||
var ipProps = nic.GetIPProperties();
|
||||
var ipv4Props = ipProps.GetIPv4Properties();
|
||||
if (ipv4Props.IsAutomaticPrivateAddressingActive)
|
||||
continue;
|
||||
|
||||
found10Net = ipProps.UnicastAddresses
|
||||
.Where(ua =>
|
||||
ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork &&
|
||||
ua.Address.GetAddressBytes()[0] == 10)
|
||||
.Any();
|
||||
if (found10Net)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found10Net)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var entry = Dns.GetHostEntry("broadband.doe.wan");
|
||||
return entry.AddressList.Length > 0;
|
||||
}
|
||||
catch (Exception)
|
||||
{ return false; } // Fail on error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries DoE VicSmart Service to detect the current site.
|
||||
|
||||
Reference in New Issue
Block a user