Repository navigation
Expand file tree
/
Copy pathDNSServer.cs
More file actions
273 lines (242 loc) · 11.7 KB
/
Copy pathDNSServer.cs
File metadata and controls
273 lines (242 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using DnsClient;
namespace NETworkManager.Models.Network;
/// <summary>
/// Class provides static information's about dns servers.
/// </summary>
public static class DNSServer
{
/// <summary>
/// Separator of the entries of a DNS server input (e.g. "Google Public DNS; 9.9.9.9").
/// </summary>
private const char EntrySeparator = NamedReferenceInput.EntrySeparator;
/// <summary>
/// Default port of a DNS server entered without a port.
/// </summary>
private const int DefaultPort = 53;
/// <summary>
/// Finds a DNS server profile by its name (case-insensitive, leading / trailing spaces are ignored).
/// </summary>
/// <param name="profiles">DNS server profiles.</param>
/// <param name="name">Name of the DNS server profile.</param>
/// <returns>DNS server profile, or null if there is none with the name.</returns>
public static DNSServerConnectionInfoProfile FindByName(IEnumerable<DNSServerConnectionInfoProfile> profiles,
string name)
{
return NamedReferenceInput.FindByName(profiles, name);
}
/// <summary>
/// Gets the entries of a DNS server input that are neither the name of a DNS server profile nor a DNS server
/// (see <see cref="ResolveServers" />), without resolving anything.
/// </summary>
/// <param name="input">DNS server input.</param>
/// <param name="profiles">DNS server profiles that can be used by their name.</param>
/// <param name="hostnameNeedsDomain">
/// A hostname must contain a dot (e.g. the run command, where a single word can also be a mistyped type).
/// </param>
/// <returns>Invalid entries, the input itself if it is empty.</returns>
public static List<string> GetInvalidEntries(string input, IEnumerable<DNSServerConnectionInfoProfile> profiles,
bool hostnameNeedsDomain = false)
{
if (string.IsNullOrWhiteSpace(input))
return [input ?? string.Empty];
var profileList = profiles.ToList();
return
[
.. input.Split(EntrySeparator)
.Where(entry => FindByName(profileList, entry) == null &&
!TryParseServer(entry, hostnameNeedsDomain, out _))
.Select(entry => entry.Trim())
];
}
/// <summary>
/// Resolves a DNS server input into DNS servers. Every entry (separated by ";") is the name of a DNS server
/// profile (e.g. "Google Public DNS", case-insensitive), which is replaced by its servers, or a DNS server (IP
/// address or hostname, optional port, e.g. "9.9.9.9" or "dns.example.com:5353") - e.g. "Google Public DNS;
/// 9.9.9.9". The Windows DNS profile in a list is replaced by the DNS servers of Windows. Every server only once.
/// </summary>
/// <param name="input">DNS server input.</param>
/// <param name="profiles">DNS server profiles that can be used by their name.</param>
/// <param name="useWindowsDNSServer">
/// True if the input is only the Windows DNS profile: the DNS servers of Windows are used (no servers are
/// returned).
/// </param>
/// <param name="invalidEntries">Entries that are neither a DNS server profile nor a DNS server (skipped).</param>
/// <returns>DNS servers (hostnames are not resolved).</returns>
public static List<ServerConnectionInfo> ResolveServers(string input,
IEnumerable<DNSServerConnectionInfoProfile> profiles, out bool useWindowsDNSServer,
out List<string> invalidEntries)
{
useWindowsDNSServer = false;
invalidEntries = [];
List<ServerConnectionInfo> servers = [];
if (string.IsNullOrWhiteSpace(input))
return servers;
var profileList = profiles.ToList();
var entries = input.Split(EntrySeparator);
foreach (var entry in entries)
{
var profile = FindByName(profileList, entry);
if (profile is { UseWindowsDNSServer: true })
{
// Only the Windows DNS profile: DNS lookup uses the DNS servers of Windows itself
if (entries.Length == 1)
{
useWindowsDNSServer = true;
return servers;
}
servers.AddRange(NameServer.ResolveNameServers(true, false).Select(x =>
new ServerConnectionInfo(x.Address, x.Port, TransportProtocol.Udp)));
}
else if (profile != null)
{
servers.AddRange(profile.Servers);
}
else if (TryParseServer(entry, false, out var server))
{
servers.Add(server);
}
else
{
invalidEntries.Add(entry.Trim());
}
}
return [.. servers.DistinctBy(x => (x.Server.ToLowerInvariant(), x.Port, x.TransportProtocol))];
}
/// <summary>
/// Replaces the names of DNS server profiles in a DNS server input by references to their ID (e.g. "Cloudflare;
/// 9.9.9.9" -> "{8f1c...}; 9.9.9.9"), so a profile still finds the DNS server profile after it was renamed. The
/// Windows DNS profile is kept by name (it exists on every computer, with another ID), other entries are kept.
/// </summary>
/// <param name="input">DNS server input (see <see cref="ResolveServers" />).</param>
/// <param name="profiles">DNS server profiles.</param>
/// <returns>Input with references, null if it contains no DNS server profile with an ID.</returns>
public static string EncodeReferences(string input, IEnumerable<DNSServerConnectionInfoProfile> profiles)
{
var profileList = profiles.ToList();
// The Windows DNS profile is kept by name
return NamedReferenceInput.EncodeReferences(input,
entry => FindByName(profileList, entry) is { UseWindowsDNSServer: false } profile ? profile : null);
}
/// <summary>
/// Replaces the references in a DNS server input (see <see cref="EncodeReferences" />) by the current names of
/// the DNS server profiles (e.g. "{8f1c...}; 9.9.9.9" -> "Cloudflare; 9.9.9.9").
/// </summary>
/// <param name="inputWithReferences">DNS server input with references.</param>
/// <param name="profiles">DNS server profiles.</param>
/// <param name="input">DNS server input with the names, null if a DNS server profile was not found.</param>
/// <returns>True if every referenced DNS server profile was found.</returns>
public static bool TryDecodeReferences(string inputWithReferences,
IEnumerable<DNSServerConnectionInfoProfile> profiles, out string input)
{
return NamedReferenceInput.TryDecodeReferences(inputWithReferences, profiles, out input);
}
/// <summary>
/// Converts a DNS server input (e.g. of a profile or group) to the values that are saved: the servers (DNS
/// server profiles replaced by their servers, see <see cref="ReplaceProfilesByServers" />, used if a DNS server
/// profile can't be found later, e.g. a profile file from another computer) and the input with references (see
/// <see cref="EncodeReferences" />).
/// </summary>
/// <param name="input">DNS server input.</param>
/// <param name="profiles">DNS server profiles.</param>
/// <returns>Servers and DNS server input with references (null if it contains no DNS server profile).</returns>
public static (string DNSServer, string DNSServerInput) Encode(string input,
IEnumerable<DNSServerConnectionInfoProfile> profiles)
{
var profileList = profiles.ToList();
return (ReplaceProfilesByServers(input?.Trim(), profileList), EncodeReferences(input, profileList));
}
/// <summary>
/// Converts the saved values (see <see cref="Encode" />) back to the DNS server input: the DNS server profiles
/// with their current name (also after a rename), or the saved servers if a DNS server profile can't be found.
/// </summary>
/// <param name="dnsServer">Saved servers.</param>
/// <param name="dnsServerInput">Saved DNS server input with references.</param>
/// <param name="profiles">DNS server profiles.</param>
/// <param name="dnsServerNotFound">True if a DNS server profile can't be found.</param>
/// <returns>DNS server input.</returns>
public static string Decode(string dnsServer, string dnsServerInput,
IEnumerable<DNSServerConnectionInfoProfile> profiles, out bool dnsServerNotFound)
{
dnsServerNotFound = false;
if (string.IsNullOrEmpty(dnsServerInput))
return dnsServer;
if (TryDecodeReferences(dnsServerInput, profiles, out var input))
return input;
dnsServerNotFound = true;
return dnsServer;
}
/// <summary>
/// Replaces the names of DNS server profiles in a DNS server input by their servers (e.g. "Cloudflare; 9.9.9.9"
/// -> "1.1.1.1:53; 1.0.0.1:53; 9.9.9.9"), so the input still works without the DNS server profiles (e.g. on another
/// computer). The Windows DNS profile and other entries are kept.
/// </summary>
/// <param name="input">DNS server input.</param>
/// <param name="profiles">DNS server profiles.</param>
/// <returns>Input with servers instead of DNS server profiles.</returns>
public static string ReplaceProfilesByServers(string input, IEnumerable<DNSServerConnectionInfoProfile> profiles)
{
if (string.IsNullOrWhiteSpace(input))
return input;
var profileList = profiles.ToList();
return string.Join("; ", input.Split(EntrySeparator).Select(entry =>
{
var profile = FindByName(profileList, entry);
return profile == null || profile.UseWindowsDNSServer
? entry.Trim()
: string.Join("; ", profile.Servers.Select(x => x.ToString()));
}));
}
/// <summary>
/// Parses a DNS server entry (IP address or hostname, optional port).
/// </summary>
private static bool TryParseServer(string entry, bool hostnameNeedsDomain, out ServerConnectionInfo server)
{
return ServerConnectionInfo.TryParse(entry, out server, DefaultPort, TransportProtocol.Udp) &&
(!hostnameNeedsDomain || IPAddress.TryParse(server.Server, out _) || server.Server.Contains('.'));
}
/// <summary>
/// Gets the default list of common dns servers.
/// </summary>
/// <returns>List of common dns servers.</returns>
public static List<DNSServerConnectionInfoProfile> GetDefaultList()
{
return
[
new() { Id = Guid.NewGuid() }, // Windows DNS server
new("Cloudflare",
[
new("1.1.1.1", 53, TransportProtocol.Udp),
new("1.0.0.1", 53, TransportProtocol.Udp)
]),
new("DNS.Watch",
[
new("84.200.69.80", 53, TransportProtocol.Udp),
new("84.200.70.40", 53, TransportProtocol.Udp)
]),
new("Google Public DNS",
[
new("8.8.8.8", 53, TransportProtocol.Udp),
new("8.8.4.4", 53, TransportProtocol.Udp)
]),
new("Level3",
[
new("209.244.0.3", 53, TransportProtocol.Udp),
new("209.244.0.4", 53, TransportProtocol.Udp)
]),
new("Quad9",
[
new ("9.9.9.9", 53, TransportProtocol.Udp),
new ("149.112.112.112", 53 , TransportProtocol.Udp)
]),
new("Verisign",
[
new("64.6.64.6", 53, TransportProtocol.Udp),
new("64.6.65.6", 53, TransportProtocol.Udp)
])
];
}
}