diff --git a/APIDOCS.md b/APIDOCS.md
index 647eae1a..575542f2 100644
--- a/APIDOCS.md
+++ b/APIDOCS.md
@@ -4,6 +4,10 @@ Technitium DNS Server provides a HTTP API which is used by the web console to pe
The URL in the documentation uses `localhost` and port `5380`. You should use the hostname/IP address and port that is specific to your DNS server instance.
+## API Request
+
+Unless it is explicitly specified, all HTTP API requests can use both `GET` or `POST` methods. When using `POST` method to pass the API parameters as form data, the `Content-Type` header must be set to `application/x-www-form-urlencoded`. When the HTTP API call is used to upload files, the call must use `POST` method and the `Content-Type` header must be set to `multipart/form-data`.
+
## API Response Format
The HTTP API returns a JSON formatted response for all requests. The JSON object returned contains `status` property which indicate if the request was successful.
@@ -63,7 +67,7 @@ None
WHERE:
- `user`: The username for the user account. The built-in administrator username on the DNS server is `admin`.
- `pass`: The password for the user account. The default password for `admin` user is `admin`.
-- `includeInfo`: Includes basic info relevant for the user in response.
+- `includeInfo` (optional): Includes basic info relevant for the user in response.
WARNING: It is highly recommended to change the password on first use to avoid security related issues.
@@ -147,7 +151,7 @@ WHERE:
Allows creating a non-expiring API token that can be used with automation scripts to make API calls. The token allows access to API calls with the same privileges as that of the user account. Thus its recommended to create a separate user account with limited permissions as required by the specific task that the token will be used for. The token cannot be used to change the user's password, or update the user profile details.
URL:\
-`http://localhost:5380/api/user/createToken?user=admin&pass=admin&tokenName=MyToken1&includeInfo=true`
+`http://localhost:5380/api/user/createToken?user=admin&pass=admin&tokenName=MyToken1`
PERMISSIONS:\
None
@@ -1540,7 +1544,7 @@ These API calls allow managing all hosted zones on the DNS server.
List all authoritative zones hosted on this DNS server. The list contains only the zones that the user has View permissions for. These API calls requires permission for both the Zones section as well as the individual permission for each zone.
URL:\
-`http://localhost:5380/api/zones/list?token=x`
+`http://localhost:5380/api/zones/list?token=x&pageNumber=1&zonesPerPage=10`
OBSOLETE PATH:\
`/api/zone/list`\
@@ -1552,11 +1556,16 @@ Zone: View
WHERE:
- `token`: The session token generated by the `login` or the `createToken` call.
+- `pageNumber` (optional): When this parameter is specified, the API will return paginated results based on the page number and zones per pages options. When not specified, the API will return a list of all zones.
+- `zonesPerPage` (optional): The number of zones per page to be returned. This option is only used when `pageNumber` options is specified. The default value is `10` when not specified.
RESPONSE:
```
{
"response": {
+ "pageNumber": 1,
+ "totalPages": 2,
+ "totalZones": 12,
"zones": [
{
"name": "",
@@ -1609,6 +1618,30 @@ RESPONSE:
"internal": true,
"dnssecStatus": "Unsigned",
"disabled": false
+ },
+ {
+ "name": "test0.com",
+ "type": "Primary",
+ "internal": false,
+ "dnssecStatus": "Unsigned",
+ "notifyFailed": false,
+ "disabled": false
+ },
+ {
+ "name": "test1.com",
+ "type": "Primary",
+ "internal": false,
+ "dnssecStatus": "Unsigned",
+ "notifyFailed": false,
+ "disabled": false
+ },
+ {
+ "name": "test2.com",
+ "type": "Primary",
+ "internal": false,
+ "dnssecStatus": "Unsigned",
+ "notifyFailed": false,
+ "disabled": false
}
]
},
@@ -1635,9 +1668,9 @@ WHERE:
- `zone`: The domain name for creating the new zone. The value can be valid domain name, an IP address, or an network address in CIDR format. When value is IP address or network address, a reverse zone is created.
- `type`: The type of zone to be created. Valid values are [`Primary`, `Secondary`, `Stub`, `Forwarder`].
- `primaryNameServerAddresses` (optional): List of comma separated IP addresses of the primary name server. This optional parameter is used only with Secondary and Stub zones. If this parameter is not used, the DNS server will try to recursively resolve the primary name server addresses automatically.
-- `zoneTransferProtocol` (optional): The zone transfer protocol to be used by secondary zones. Valid values are [`Tcp`, `Tls`].
+- `zoneTransferProtocol` (optional): The zone transfer protocol to be used by secondary zones. Valid values are [`Tcp`, `Tls`, `Quic`].
- `tsigKeyName` (optional): The TSIG key name to be used by secondary zones.
-- `protocol` (optional): The DNS transport protocol to be used by the conditional forwarder zone. This optional parameter is used with Conditional Forwarder zones. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`]. Default `Udp` protocol is used when this parameter is missing.
+- `protocol` (optional): The DNS transport protocol to be used by the conditional forwarder zone. This optional parameter is used with Conditional Forwarder zones. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `Quic`]. Default `Udp` protocol is used when this parameter is missing.
- `forwarder` (optional): The address of the DNS server to be used as a forwarder. This optional parameter is required to be used with Conditional Forwarder zones. A special value `this-server` can be used as a forwarder which when used will forward all the requests internally to this DNS server such that you can override the zone with records and rest of the zone gets resolved via This Server.
- `dnssecValidation` (optional): Set this boolean value to indicate if DNSSEC validation must be done. This optional parameter is required to be used with Conditional Forwarder zones.
- `proxyType` (optional): The type of proxy that must be used for conditional forwarding. This optional parameter is required to be used with Conditional Forwarder zones. Valid values are [`None`, `Http`, `Socks5`]. Default value `None` is used when this parameter is missing.
@@ -1992,7 +2025,7 @@ WHERE:
- `zskKeySize` (optional): The size of the Zone Signing Key (ZSK) in bits to be used when using `RSA` algorithm. This optional parameter is required when using `RSA` algorithm.
- `curve` (optional): The name of the curve to be used when using `ECDSA` algorithm. Valid values are [`P256`, `P384`]. This optional parameter is required when using `ECDSA` algorithm.
- `dnsKeyTtl` (optional): The TTL value to be used for DNSKEY records. Default value is `86400` when not specified.
-- `zskRolloverDays` (optional): The frequency in days that the DNS server must automatically rollover the Zone Signing Keys (ZSK) in the zone. Valid range is 0-365 days where 0 disables rollover. Default value is `90` days.
+- `zskRolloverDays` (optional): The frequency in days that the DNS server must automatically rollover the Zone Signing Keys (ZSK) in the zone. Valid range is 0-365 days where 0 disables rollover. Default value is `90` when not specified.
- `nxProof` (optional): The type of proof of non-existence that must be used for signing the zone. Valid values are [`NSEC`, `NSEC3`]. Default value is `NSEC` when not specified.
- `iterations` (optional): The number of iterations to use for hashing in NSEC3. This optional parameter is only applicable when using `NSEC3` as the `nxProof`. Default value is `0` when not specified.
- `saltLength` (optional): The length of salt in bytes to use for hashing in NSEC3. This optional parameter is only applicable when using `NSEC3` as the `nxProof`. Default value is `0` when not specified.
@@ -2056,23 +2089,24 @@ RESPONSE:
"internal": false,
"disabled": false,
"dnssecStatus": "SignedWithNSEC",
- "dnsKeyTtl": 86400,
+ "dnsKeyTtl": 3600,
"dnssecPrivateKeys": [
{
- "keyTag": 19198,
+ "keyTag": 15048,
"keyType": "KeySigningKey",
"algorithm": "ECDSAP256SHA256",
- "state": "Ready",
- "stateChangedOn": "2022-02-19T06:53:21Z",
+ "state": "Published",
+ "stateChangedOn": "2022-12-18T14:39:50.0328321Z",
+ "stateReadyBy": "2022-12-18T16:14:50.0328321Z",
"isRetiring": false,
"rolloverDays": 0
},
{
- "keyTag": 50617,
+ "keyTag": 46152,
"keyType": "ZoneSigningKey",
"algorithm": "ECDSAP256SHA256",
"state": "Active",
- "stateChangedOn": "2022-02-19T06:53:21Z",
+ "stateChangedOn": "2022-12-18T14:39:50.0661173Z",
"isRetiring": false,
"rolloverDays": 90
}
@@ -2398,7 +2432,7 @@ WHERE:
- `tag` (optional): This parameter is required for adding the `CAA` record.
- `value` (optional): This parameter is required for adding the `CAA` record.
- `aname` (optional): The ANAME domain name. This option is required for adding `ANAME` record.
-- `protocol` (optional): This parameter is required for adding the `FWD` record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`].
+- `protocol` (optional): This parameter is required for adding the `FWD` record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `Quic`].
- `forwarder` (optional): The forwarder address. A special value of `this-server` can be used to directly forward requests internally to the DNS server. This parameter is required for adding the `FWD` record.
- `dnssecValidation` (optional): Set this boolean value to indicate if DNSSEC validation must be done. This optional parameter is to be used with FWD records. Default value is `false`.
- `proxyType` (optional): The type of proxy that must be used for conditional forwarding. This optional parameter is to be used with FWD records. Valid values are [`None`, `Http`, `Socks5`]. Default value `None` is used when this parameter is missing.
@@ -2442,7 +2476,7 @@ RESPONSE:
Gets all records for a given authoritative zone.
URL:\
-`http://localhost:5380/api/zones/records/get?token=x&domain=example.com&zone=example.com`
+`http://localhost:5380/api/zones/records/get?token=x&domain=example.com&zone=example.com&listZone=true`
OBSOLETE PATH:\
`/api/zone/getRecords`\
@@ -2456,6 +2490,7 @@ WHERE:
- `token`: The session token generated by the `login` or the `createToken` call.
- `domain`: The domain name of the zone to get records.
- `zone` (optional): The name of the authoritative zone into which the `domain` exists. When unspecified, the closest authoritative zone will be used.
+- `listZone` (optional): When set to `true` will list all records in the zone else will list records only for the given domain name. Default value is `false` when not specified.
RESPONSE:
```
@@ -2626,10 +2661,6 @@ RESPONSE:
"computedKeyTag": 65078,
"dnsKeyState": "Ready",
"computedDigests": [
- {
- "digestType": "SHA1",
- "digest": "3C2B05EBC20B77D8BC56EB9FFB36A6A1F07983F9"
- },
{
"digestType": "SHA256",
"digest": "BBE017B17E5CB5FFFF1EC2C7815367DF80D8E7EAEE4832D3ED192159D79B1EEB"
@@ -2661,30 +2692,28 @@ RESPONSE:
"disabled": false,
"name": "example.com",
"type": "DNSKEY",
- "ttl": 86400,
+ "ttl": 3600,
"rData": {
"flags": "SecureEntryPoint, ZoneKey",
"protocol": 3,
"algorithm": "ECDSAP256SHA256",
- "publicKey": "KtXitZeC9ijbghCwQ5kjBfgLxCa0pBOOBGftudxGv/I= hlRGy7/Plea39T8n78xiHPaspYrTcdyidbKz6Z+ZSGw=",
- "computedKeyTag": 52896,
+ "publicKey": "KOJWFitKm58EgjO43GDnsFbnkGoqVKeLRkP8FGPAdhqA2F758Ta1mkxieEu0YN0EoX+u5bVuc5DEBFSv+U63CA==",
+ "computedKeyTag": 15048,
"dnsKeyState": "Published",
+ "dnsKeyStateReadyBy": "2022-12-18T16:14:50.0328321Z",
"computedDigests": [
- {
- "digestType": "SHA1",
- "digest": "767EA31AD77C6AC2ACEB22B3FADB033679A6FB79"
- },
{
"digestType": "SHA256",
- "digest": "BDBDB532C5809F890F8092DC9702D763D51A7318887B195CB52E888882FBE373"
+ "digest": "8EAFAE3305DB57A27CA5A261525515461CB7232A34A44AD96441B88BCA9B9849"
},
{
"digestType": "SHA384",
- "digest": "A7EA4C7816ED5F011FDF90015D4A37BC7D1C773C22C3440B57D6717FA4ED71C5B95D09592AF48BD3ED59028D214A367E"
+ "digest": "4A6DA59E91872B5B835FCEE5987B17151A6F10FE409B595BEEEDB28FE64315C9C268493B59A0BF72EA84BE0F20A33F96"
}
]
},
- "dnssecStatus": "Unknown"
+ "dnssecStatus": "Unknown",
+ "lastUsedOn": "0001-01-01T00:00:00"
},
{
"disabled": false,
@@ -3019,7 +3048,7 @@ WHERE:
- `expire` (optional): This is the expire parameter in the SOA record. This parameter is required when updating the SOA record.
- `minimum` (optional): This is the minimum parameter in the SOA record. This parameter is required when updating the SOA record.
- `primaryAddresses` (optional): This is a comma separated list of IP addresses of the primary name server. This parameter is to be used with secondary and stub zones where the primary name server address is not directly resolvable.
-- `zoneTransferProtocol` (optional): The zone transfer protocol to be used by the secondary zone. Valid values are [`Tcp`, `Tls`]. This parameter is used with `SOA` record.
+- `zoneTransferProtocol` (optional): The zone transfer protocol to be used by the secondary zone. Valid values are [`Tcp`, `Tls`, `Quic`]. This parameter is used with `SOA` record.
- `tsigKeyName` (optional): The TSIG key name to be used by the secondary zone. This parameter is used with `SOA` record.
- `ptrName`(optional): The current PTR domain name. This option is required for updating `PTR` record.
- `newPtrName`(optional): The new PTR domain name. This option is required for updating `PTR` record.
@@ -3068,8 +3097,8 @@ WHERE:
- `newValue` (optional): The new value in CAA record. This parameter is required when updating the `CAA` record.
- `aname` (optional): The current ANAME domain name. This parameter is required when updating the `ANAME` record.
- `newAName` (optional): The new ANAME domain name. This parameter is required when updating the `ANAME` record.
-- `protocol` (optional): This is the current protocol value in the FWD record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`]. This parameter is optional and default value `Udp` will be used when updating the `FWD` record.
-- `newProtocol` (optional): This is the new protocol value in the FWD record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`]. This parameter is optional and default value `Udp` will be used when updating the `FWD` record.
+- `protocol` (optional): This is the current protocol value in the FWD record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `Quic`]. This parameter is optional and default value `Udp` will be used when updating the `FWD` record.
+- `newProtocol` (optional): This is the new protocol value in the FWD record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `Quic`]. This parameter is optional and default value `Udp` will be used when updating the `FWD` record.
- `forwarder` (optional): The current forwarder address. This parameter is required when updating the `FWD` record.
- `newForwarder` (optional): The new forwarder address. This parameter is required when updating the `FWD` record.
- `dnssecValidation` (optional): Set this boolean value to indicate if DNSSEC validation must be done. This optional parameter is to be used with FWD records. Default value is `false`.
@@ -3160,7 +3189,7 @@ WHERE:
- `tag` (optional): This is the tag parameter in the CAA record. This parameter is required when deleting the `CAA` record.
- `value` (optional): This parameter is required when deleting the `CAA` record.
- `aname` (optional): This parameter is required when deleting the `ANAME` record.
-- `protocol` (optional): This is the protocol parameter in the FWD record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`]. This parameter is optional and default value `Udp` will be used when deleting the `FWD` record.
+- `protocol` (optional): This is the protocol parameter in the FWD record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `Quic`]. This parameter is optional and default value `Udp` will be used when deleting the `FWD` record.
- `forwarder` (optional): This parameter is required when deleting the `FWD` record.
RESPONSE:
@@ -4003,7 +4032,7 @@ WHERE:
- `server`: The name server to query using the DNS client.
- `domain`: The domain name to query.
- `type`: The type of the query.
-- `protocol` (optional): The DNS transport protocol to be used to query. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`]. The default value of `Udp` is used when the parameter is missing.
+- `protocol` (optional): The DNS transport protocol to be used to query. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `Quic`]. The default value of `Udp` is used when the parameter is missing.
- `dnssec` (optional): Set to `true` to enable DNSSEC validation.
- `import` (optional): This parameter when set to `true` indicates that the response of the DNS query should be imported in the an authoritative zone on this DNS server. Default value is `false` when this parameter is missing. If a zone does not exists, a primary zone for the `domain` name is created and the records from the response are set into the zone. Import can be done only for primary and forwarder type of zones. When `type` is set to AXFR, then the import feature will work as if a zone transfer was requested and the complete zone will be updated as per the zone transfer response. Note that any existing record type for the given `type` will be overwritten when syncing the records. It is recommended to use `recursive-resolver` or the actual name server address for the `server` parameter when importing records. You must have Zones Modify permission to create a zone or Zone Modify permission to import records into an existing zone.
@@ -4084,29 +4113,12 @@ RESPONSE:
```
{
"response": {
- "version": "10.0",
+ "version": "11.0",
"dnsServerDomain": "server1",
"dnsServerLocalEndPoints": [
"0.0.0.0:53",
"[::]:53"
],
- "webServiceLocalAddresses": [
- "0.0.0.0",
- "[::]"
- ],
- "webServiceHttpPort": 5380,
- "webServiceEnableTls": false,
- "webServiceHttpToTlsRedirect": false,
- "webServiceTlsPort": 53443,
- "webServiceUseSelfSignedTlsCertificate": false,
- "webServiceTlsCertificatePath": "",
- "webServiceTlsCertificatePassword": "************",
- "enableDnsOverHttp": false,
- "enableDnsOverTls": false,
- "enableDnsOverHttps": false,
- "dnsTlsCertificatePath": null,
- "dnsTlsCertificatePassword": "************",
- "tsigKeys": [],
"defaultRecordTtl": 3600,
"dnsAppsEnableAutomaticUpdate": true,
"preferIPv6": false,
@@ -4115,32 +4127,48 @@ RESPONSE:
"eDnsClientSubnet": false,
"eDnsClientSubnetIPv4PrefixLength": 24,
"eDnsClientSubnetIPv6PrefixLength": 56,
- "resolverRetries": 2,
- "resolverTimeout": 2000,
- "resolverMaxStackCount": 16,
- "forwarderRetries": 3,
- "forwarderTimeout": 2000,
- "forwarderConcurrency": 2,
+ "qpmLimitRequests": 0,
+ "qpmLimitErrors": 0,
+ "qpmLimitSampleMinutes": 5,
+ "qpmLimitIPv4PrefixLength": 24,
+ "qpmLimitIPv6PrefixLength": 56,
"clientTimeout": 4000,
"tcpSendTimeout": 10000,
"tcpReceiveTimeout": 10000,
- "enableLogging": true,
- "logQueries": false,
- "useLocalTime": false,
- "logFolder": "logs",
- "maxLogFileDays": 0,
- "maxStatFileDays": 0,
+ "quicIdleTimeout": 60000,
+ "quicMaxInboundStreams": 100,
+ "listenBacklog": 100,
+ "webServiceLocalAddresses": [
+ "[::]"
+ ],
+ "webServiceHttpPort": 5380,
+ "webServiceEnableTls": false,
+ "webServiceHttpToTlsRedirect": false,
+ "webServiceUseSelfSignedTlsCertificate": false,
+ "webServiceTlsPort": 53443,
+ "webServiceTlsCertificatePath": null,
+ "webServiceTlsCertificatePassword": "************",
+ "enableDnsOverHttp": true,
+ "enableDnsOverTls": true,
+ "enableDnsOverHttps": true,
+ "enableDnsOverQuic": false,
+ "dnsOverHttpPort": 8053,
+ "dnsOverTlsPort": 853,
+ "dnsOverHttpsPort": 443,
+ "dnsOverQuicPort": 853,
+ "dnsTlsCertificatePath": "z:\\ns2.technitium.com.pfx",
+ "dnsTlsCertificatePassword": "************",
+ "tsigKeys": [],
"recursion": "AllowOnlyForPrivateNetworks",
"recursionDeniedNetworks": [],
"recursionAllowedNetworks": [],
"randomizeName": true,
"qnameMinimization": true,
"nsRevalidation": true,
- "qpmLimitRequests": 0,
- "qpmLimitErrors": 0,
- "qpmLimitSampleMinutes": 5,
- "qpmLimitIPv4PrefixLength": 24,
- "qpmLimitIPv6PrefixLength": 56,
+ "resolverRetries": 2,
+ "resolverTimeout": 2000,
+ "resolverMaxStackCount": 16,
+ "saveCache": false,
"serveStale": true,
"serveStaleTtl": 259200,
"cacheMaximumEntries": 10000,
@@ -4152,15 +4180,24 @@ RESPONSE:
"cachePrefetchTrigger": 9,
"cachePrefetchSampleIntervalInMinutes": 5,
"cachePrefetchSampleEligibilityHitsPerHour": 30,
- "proxy": null,
- "forwarders": null,
- "forwarderProtocol": "Udp",
"enableBlocking": true,
"allowTxtBlockingReport": true,
"blockingType": "AnyAddress",
"customBlockingAddresses": [],
"blockListUrls": null,
- "blockListUpdateIntervalHours": 24
+ "blockListUpdateIntervalHours": 24,
+ "proxy": null,
+ "forwarders": null,
+ "forwarderProtocol": "Udp",
+ "forwarderRetries": 3,
+ "forwarderTimeout": 2000,
+ "forwarderConcurrency": 2,
+ "enableLogging": true,
+ "logQueries": false,
+ "useLocalTime": false,
+ "logFolder": "logs",
+ "maxLogFileDays": 0,
+ "maxStatFileDays": 0
},
"status": "ok"
}
@@ -4183,64 +4220,69 @@ WHERE:
- `token`: The session token generated by the `login` or the `createToken` call.
- `dnsServerDomain` (optional): The primary domain name used by this DNS Server to identify itself.
- `dnsServerLocalEndPoints` (optional): Local end points are the network interface IP addresses and ports you want the DNS Server to listen for requests.
+- `defaultRecordTtl` (optional): The default TTL value to use if not specified when adding or updating records in a Zone.
+- `dnsAppsEnableAutomaticUpdate` (optional): Set to `true` to allow DNS server to automatically update the DNS Apps from the DNS App Store. The DNS Server will check for updates every 24 hrs when this option is enabled.
+- `preferIPv6` (optional): DNS Server will use IPv6 for querying whenever possible with this option enabled. Initial value is `false`.
+- `udpPayloadSize` (optional): The maximum EDNS UDP payload size that can be used to avoid IP fragmentation. Valid range is 512-4096 bytes. Initial value is `1232`.
+- `dnssecValidation` (optional): Set this to `true` to enable DNSSEC validation. DNS Server will validate all responses from name servers or forwarders when this option is enabled.
+- `eDnsClientSubnet` (optional): Set this to `true` to enable EDNS Client Subnet. DNS Server will use the public IP address of the request with a prefix length, or the existing Client Subnet option from the request while resolving requests.
+- `eDnsClientSubnetIPv4PrefixLength` (optional): The EDNS Client Subnet IPv4 prefix length to define the client subnet. Initial value is `24`.
+- `eDnsClientSubnetIPv6PrefixLength` (optional): The EDNS Client Subnet IPv6 prefix length to define the client subnet. Initial value is `56`.
+- `qpmLimitRequests` (optional): Sets the Queries Per Minute (QPM) limit on total number of requests that is enforces per client subnet. Set value to `0` to disable the feature.
+- `qpmLimitErrors` (optional): Sets the Queries Per Minute (QPM) limit on total number of requests which generates an error response that is enforces per client subnet. Set value to `0` to disable the feature. Response with an RCODE of FormatError, ServerFailure, or Refused is considered as an error response.
+- `qpmLimitSampleMinutes` (optional): Sets the client query stats sample size in minutes for QPM limit feature. Initial value is `5`.
+- `qpmLimitIPv4PrefixLength` (optional): Sets the client subnet IPv4 prefix length used to define the subnet. Initial value is `24`.
+- `qpmLimitIPv6PrefixLength` (optional): Sets the client subnet IPv6 prefix length used to define the subnet. Initial value is `56`.
+- `clientTimeout` (optional): The amount of time the DNS server must wait in milliseconds before responding with a ServerFailure response to a client request when no answer is available. Valid range is `1000`-`10000`. Initial value is `4000`.
+- `tcpSendTimeout` (optional): The amount of time in milliseconds a TCP socket must wait for an ACK before closing the connection. This option will apply for DNS requests being received by the DNS Server over TCP, TLS, or HTTPS transports. Valid range is `1000`-`90000`. Initial value is `10000`.
+- `tcpReceiveTimeout` (optional): The amount of time in milliseconds a TCP socket must wait for data before closing the connection. This option will apply for DNS requests being received by the DNS Server over TCP, TLS, or HTTPS transports. Valid range is `1000`-`90000`. Initial value is `10000`.
+- `quicIdleTimeout` (optional): The time interval in milliseconds after which an idle QUIC connection will be closed. This option applies only to QUIC transport protocol. Valid range is `1000`-`90000`. Initial value is `60000`.
+- `quicMaxInboundStreams` (optional): The max number of inbound bidirectional streams that can be accepted per QUIC connection. This option applies only to QUIC transport protocol. Valid range is `1`-`1000`. Initial value is `100`.
+- `listenBacklog` (optional): The maximum number of pending connections. This option applies to TCP, TLS, and QUIC transport protocols. Initial value is `100`.
- `webServiceLocalAddresses` (optional): Local addresses are the network interface IP addresses you want the web service to listen for requests.
-- `webServiceHttpPort` (optional): Specify the TCP port number for the web console and this API web service. Default value is `5380`.
-- `webServiceEnableTls` (optional): Set this to `true` to start the HTTPS service to acccess web service.
+- `webServiceHttpPort` (optional): Specify the TCP port number for the web console and this API web service. Initial value is `5380`.
+- `webServiceEnableTls` (optional): Set this to `true` to start the HTTPS service to access web service.
- `webServiceTlsPort` (optional): Specified the TCP port number for the web console for HTTPS access.
- `webServiceUseSelfSignedTlsCertificate` (optional): Set `true` for the web service to use an automatically generated self signed certificate when TLS certificate path is not specified.
- `webServiceTlsCertificatePath` (optional): Specify a PKCS #12 certificate (.pfx) file path on the server. The certificate must contain private key. This certificate is used by the web console for HTTPS access.
- `webServiceTlsCertificatePassword` (optional): Enter the certificate (.pfx) password, if any.
-- `enableDnsOverHttp` (optional): Enable this option to accept DNS-over-HTTP requests for both wire and json response formats. It must be used with a TLS terminating reverse proxy like nginx and will work only on private networks.
+- `enableDnsOverHttp` (optional): Enable this option to accept DNS-over-HTTP requests. It must be used with a TLS terminating reverse proxy like nginx and will work only on private networks. Enabling this option also allows automatic TLS certificate renewal with HTTP challenge (webroot) for DNS-over-HTTPS service.
- `enableDnsOverTls` (optional): Enable this option to accept DNS-over-TLS requests.
-- `enableDnsOverHttps` (optional): Enable this option to accept DNS-over-HTTPS requests for both wire and json response formats.
+- `enableDnsOverHttps` (optional): Enable this option to accept DNS-over-HTTPS requests.
+- `enableDnsOverQuic` (optional): Enable this option to accept DNS-over-QUIC requests.
+- `dnsOverHttpPort` (optional): The TCP port number for DNS-over-HTTP protocol. Initial value is `80`.
+- `dnsOverTlsPort` (optional): The TCP port number for DNS-over-TLS protocol. Initial value is `853`.
+- `dnsOverHttpsPort` (optional): The TCP port number for DNS-over-HTTPS protocol. Initial value is `443`.
+- `dnsOverQuicPort` (optional): The UDP port number for DNS-over-QUIC protocol. Initial value is `853`.
- `dnsTlsCertificatePath` (optional): Specify a PKCS #12 certificate (.pfx) file path on the server. The certificate must contain private key. This certificate is used by the DNS-over-TLS and DNS-over-HTTPS optional protocols.
- `dnsTlsCertificatePassword` (optional): Enter the certificate (.pfx) password, if any.
- `tsigKeys` (optional): A pipe `|` separated multi row list of TSIG key name, shared secret, and algorithm. Set this parameter to `false` to remove all existing keys. Supported algorithms are [`hmac-md5.sig-alg.reg.int`, `hmac-sha1`, `hmac-sha256`, `hmac-sha256-128`, `hmac-sha384`, `hmac-sha384-192`, `hmac-sha512`, `hmac-sha512-256`].
-- `defaultRecordTtl` (optional): The default TTL value to use if not specified when adding or updating records in a Zone.
-- `dnsAppsEnableAutomaticUpdate` (optional): Set to `true` to allow DNS server to automatically update the DNS Apps from the DNS App Store. The DNS Server will check for updates every 24 hrs when this option is enabled.
-- `preferIPv6` (optional): DNS Server will use IPv6 for querying whenever possible with this option enabled. Default value is `false`.
-- `udpPayloadSize` (optional): The maximum EDNS UDP payload size that can be used to avoid IP fragmentation. Valid range is 512-4096 bytes. Default value is `1232`.
-- `dnssecValidation` (optional): Set this to `true` to enable DNSSEC validation. DNS Server will validate all responses from name servers or forwarders when this option is enabled.
-- `eDnsClientSubnet` (optional): Set this to `true` to enable EDNS Client Subnet. DNS Server will use the public IP address of the request with a prefix length, or the existing Client Subnet option from the request while resolving requests.
-- `eDnsClientSubnetIPv4PrefixLength` (optional): The EDNS Client Subnet IPv4 prefix length to define the client subnet. Default value is `24`.
-- `eDnsClientSubnetIPv6PrefixLength` (optional): The EDNS Client Subnet IPv6 prefix length to define the client subnet. Default value is `56`.
-- `resolverRetries` (optional): The number of retries that the recursive resolver must do.
-- `resolverTimeout` (optional): The timeout value in milliseconds for the recursive resolver.
-- `resolverMaxStackCount` (optional): The max stack count that the recursive resolver must use.
-- `forwarderRetries` (optional): The number of retries that the forwarder DNS client must do.
-- `forwarderTimeout` (optional): The timeout value in milliseconds for the forwarder DNS client.
-- `forwarderConcurrency` (optional): The number of concurrent requests that the forwarder DNS client should do.
-- `clientTimeout` (optional): The amount of time the DNS server must wait in milliseconds before responding with a ServerFailure response to a client request when no answer is available.
-- `tcpSendTimeout` (optional): The amount of time in milliseconds a TCP socket must wait for an ACK before closing the connection. This option will apply for DNS requests being received by the DNS Server over TCP, TLS, or HTTPS transports.
-- `tcpReceiveTimeout` (optional): The amount of time in milliseconds a TCP socket must wait for data before closing the connection. This option will apply for DNS requests being received by the DNS Server over TCP, TLS, or HTTPS transports.
-- `enableLogging` (optional): Enable this option to log error and audit logs into the log file. Default value is `true`.
-- `logQueries` (optional): Enable this option to log every query received by this DNS Server and the corresponding response answers into the log file. Default value is `false`.
-- `useLocalTime` (optional): Enable this option to use local time instead of UTC for logging. Default value is `false`.
-- `logFolder` (optional): The folder path on the server where the log files should be saved. The path can be relative to the DNS server config folder. Default value is `logs`.
-- `maxLogFileDays` (optional): Max number of days to keep the log files. Log files older than the specified number of days will be deleted automatically. Recommended value is `365`. Set `0` to disable auto delete.
-- `maxStatFileDays` (optional): Max number of days to keep the dashboard stats. Stat files older than the specified number of days will be deleted automatically. Recommended value is `365`. Set `0` to disable auto delete.
- `recursion` (optional): Sets the recursion policy for the DNS server. Valid values are [`Deny`, `Allow`, `AllowOnlyForPrivateNetworks`, `UseSpecifiedNetworks`].
- `recursionDeniedNetworks` (optional): A comma separated list of network addresses in CIDR format that must be denied recursion. Set this parameter to `false` to remove existing values. These values are only used when `recursion` is set to `UseSpecifiedNetworks`.
- `recursionAllowedNetworks` (optional): A comma separated list of network addresses in CIDR format that must be allowed recursion. Set this parameter to `false` to remove existing values. These values are only used when `recursion` is set to `UseSpecifiedNetworks`.
-- `randomizeName` (optional): Enables QNAME randomization [draft-vixie-dnsext-dns0x20-00](https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00) when using UDP as the transport protocol. Default value is `true`.
-- `qnameMinimization` (optional): Enables QNAME minimization [draft-ietf-dnsop-rfc7816bis-04](https://tools.ietf.org/html/draft-ietf-dnsop-rfc7816bis-04) when doing recursive resolution. Default value is `true`.
-- `nsRevalidation` (optional): Enables [draft-ietf-dnsop-ns-revalidation](https://datatracker.ietf.org/doc/draft-ietf-dnsop-ns-revalidation/) for recursive resolution. Default value is `true`.
-- `qpmLimitRequests` (optional): Sets the Queries Per Minute (QPM) limit on total number of requests that is enforces per client subnet. Set value to `0` to disable the feature.
-- `qpmLimitErrors` (optional): Sets the Queries Per Minute (QPM) limit on total number of requests which generates an error response that is enforces per client subnet. Set value to `0` to disable the feature. Response with an RCODE of FormatError, ServerFailure, or Refused is considered as an error response.
-- `qpmLimitSampleMinutes` (optional): Sets the client query stats sample size in minutes for QPM limit feature. Default value is `5`.
-- `qpmLimitIPv4PrefixLength` (optional): Sets the client subnet IPv4 prefix length used to define the subnet. Default value is `24`.
-- `qpmLimitIPv6PrefixLength` (optional): Sets the client subnet IPv6 prefix length used to define the subnet. Default value is `56`.
-- `serveStale` (optional): Enable the serve stale feature to improve resiliency by using expired or stale records in cache when the DNS server is unable to reach the upstream or authoritative name servers. Default value is `true`.
-- `serveStaleTtl` (optional): The TTL value in seconds which should be used for cached records that are expired. When the serve stale TTL too expires for a stale record, it gets removed from the cache. Recommended value is between 1-3 days and maximum supported value is 7 days. Default value is `259200`.
-- `temporaryDisableBlockingTill` (read only): An ISO 8601 String with the Date and Time when the Temporary Blocking will end.
-- `cacheMinimumRecordTtl` (optional): The minimum TTL value that a record can have in cache. Set a value to make sure that the records with TTL value than it stays in cache for a minimum duration. Default value is `10`.
-- `cacheMaximumRecordTtl` (optional): The maximum TTL value that a record can have in cache. Set a lower value to allow the records to expire early. Default value is `86400`.
-- `cacheNegativeRecordTtl` (optional): The negative TTL value to use when there is no SOA MINIMUM value available. Default value is `300`.
-- `cacheFailureRecordTtl` (optional): The failure TTL value to used for caching failure responses. This allows storing failure record in cache and prevent frequent recursive resolution to name servers that are responding with `ServerFailure`. Default value is `60`.
+- `randomizeName` (optional): Enables QNAME randomization [draft-vixie-dnsext-dns0x20-00](https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00) when using UDP as the transport protocol. Initial value is `true`.
+- `qnameMinimization` (optional): Enables QNAME minimization [draft-ietf-dnsop-rfc7816bis-04](https://tools.ietf.org/html/draft-ietf-dnsop-rfc7816bis-04) when doing recursive resolution. Initial value is `true`.
+- `nsRevalidation` (optional): Enables [draft-ietf-dnsop-ns-revalidation](https://datatracker.ietf.org/doc/draft-ietf-dnsop-ns-revalidation/) for recursive resolution. Initial value is `true`.
+- `resolverRetries` (optional): The number of retries that the recursive resolver must do.
+- `resolverTimeout` (optional): The timeout value in milliseconds for the recursive resolver.
+- `resolverMaxStackCount` (optional): The max stack count that the recursive resolver must use.
+- `saveCache` (optional): Enable this option to save DNS cache on disk when the DNS server stops. The saved cache will be loaded next time the DNS server starts.
+- `serveStale` (optional): Enable the serve stale feature to improve resiliency by using expired or stale records in cache when the DNS server is unable to reach the upstream or authoritative name servers. Initial value is `true`.
+- `serveStaleTtl` (optional): The TTL value in seconds which should be used for cached records that are expired. When the serve stale TTL too expires for a stale record, it gets removed from the cache. Recommended value is between 1-3 days and maximum supported value is 7 days. Initial value is `259200`.
+- `cacheMinimumRecordTtl` (optional): The minimum TTL value that a record can have in cache. Set a value to make sure that the records with TTL value than it stays in cache for a minimum duration. Initial value is `10`.
+- `cacheMaximumRecordTtl` (optional): The maximum TTL value that a record can have in cache. Set a lower value to allow the records to expire early. Initial value is `86400`.
+- `cacheNegativeRecordTtl` (optional): The negative TTL value to use when there is no SOA MINIMUM value available. Initial value is `300`.
+- `cacheFailureRecordTtl` (optional): The failure TTL value to used for caching failure responses. This allows storing failure record in cache and prevent frequent recursive resolution to name servers that are responding with `ServerFailure`. Initial value is `60`.
- `cachePrefetchEligibility` (optional): The minimum initial TTL value of a record needed to be eligible for prefetching.
- `cachePrefetchTrigger` (optional): A record with TTL value less than trigger value will initiate prefetch operation immediately for itself. Set `0` to disable prefetching & auto prefetching.
- `cachePrefetchSampleIntervalInMinutes` (optional): The interval to sample eligible domain names from last hour stats for auto prefetch.
- `cachePrefetchSampleEligibilityHitsPerHour` (optional): Minimum required hits per hour for a domain name to be eligible for auto prefetch.
+- `enableBlocking` (optional): Sets the DNS server to block domain names using Blocked Zone and Block List Zone.
+- `allowTxtBlockingReport` (optional): Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests.
+- `blockingType` (optional): Sets how the DNS server should respond to a blocked domain request. Valid values are [`AnyAddress`, `NxDomain`, `CustomAddress`] where `AnyAddress` is default which response with `0.0.0.0` and `::` IP addresses for blocked domains. Using `NxDomain` will respond with `NX Domain` response. `CustomAddress` will return the specified custom blocking addresses.
+- `customBlockingAddresses` (optional): Set the custom blocking addresses to be used for blocked domain response. These addresses are returned only when `blockingType` is set to `CustomAddress`.
+- `blockListUrls` (optional): A comma separated list of block list URLs that this server must automatically download and use with the block lists zone. DNS Server will use the data returned by the block list URLs to update the block list zone automatically every 24 hours. The expected file format is standard hosts file format or plain text file containing list of domains to block. Set this parameter to `false` to remove existing values.
+- `blockListUpdateIntervalHours` (optional): The interval in hours to automatically download and update the block lists. Initial value is `24`.
- `proxyType` (optional): The type of proxy protocol to be used. Valid values are [`None`, `Http`, `Socks5`].
- `proxyAddress` (optional): The proxy server hostname or IP address.
- `proxyPort` (optional): The proxy server port.
@@ -4248,14 +4290,16 @@ WHERE:
- `proxyPassword` (optional): The proxy server password.
- `proxyBypass` (optional): A comma separated bypass list consisting of IP addresses, network addresses in CIDR format, or host/domain names to never use proxy for.
- `forwarders` (optional): A comma separated list of forwarders to be used by this DNS server. Set this parameter to `false` string to remove existing forwarders so that the DNS server does recursive resolution by itself.
-- `forwarderProtocol` (optional): The forwarder DNS transport protocol to be used. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`].
-- `enableBlocking` (optional): Sets the DNS server to block domain names using Blocked Zone and Block List Zone.
-- `allowTxtBlockingReport` (optional): Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests.
-- `blockingType` (optional): Sets how the DNS server should respond to a blocked domain request. Valid values are [`AnyAddress`, `NxDomain`, `CustomAddress`] where `AnyAddress` is default which response with `0.0.0.0` and `::` IP addresses for blocked domains. Using `NxDomain` will respond with `NX Domain` response. `CustomAddress` will return the specified custom blocking addresses.
-- `blockListNextUpdatedOn` (read only): An ISO 8601 String with the Date and Time when the blocklist will next be updated.
-- `customBlockingAddresses` (optional): Set the custom blocking addresses to be used for blocked domain response. These addresses are returned only when `blockingType` is set to `CustomAddress`.
-- `blockListUrls` (optional): A comma separated list of block list URLs that this server must automatically download and use with the block lists zone. DNS Server will use the data returned by the block list URLs to update the block list zone automatically every 24 hours. The expected file format is standard hosts file format or plain text file containing list of domains to block. Set this parameter to `false` to remove existing values.
-- `blockListUpdateIntervalHours` (optional): The interval in hours to automatically download and update the block lists. Default value is `24`.
+- `forwarderProtocol` (optional): The forwarder DNS transport protocol to be used. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `Quic`].
+- `forwarderRetries` (optional): The number of retries that the forwarder DNS client must do.
+- `forwarderTimeout` (optional): The timeout value in milliseconds for the forwarder DNS client.
+- `forwarderConcurrency` (optional): The number of concurrent requests that the forwarder DNS client should do.
+- `enableLogging` (optional): Enable this option to log error and audit logs into the log file. Initial value is `true`.
+- `logQueries` (optional): Enable this option to log every query received by this DNS Server and the corresponding response answers into the log file. Initial value is `false`.
+- `useLocalTime` (optional): Enable this option to use local time instead of UTC for logging. Initial value is `false`.
+- `logFolder` (optional): The folder path on the server where the log files should be saved. The path can be relative to the DNS server config folder. Initial value is `logs`.
+- `maxLogFileDays` (optional): Max number of days to keep the log files. Log files older than the specified number of days will be deleted automatically. Recommended value is `365`. Set `0` to disable auto delete.
+- `maxStatFileDays` (optional): Max number of days to keep the dashboard stats. Stat files older than the specified number of days will be deleted automatically. Recommended value is `365`. Set `0` to disable auto delete.
RESPONSE:
This call returns the newly updated settings in the same format as that of the `getDnsSettings` call.
@@ -4611,6 +4655,11 @@ RESPONSE:
"pingCheckTimeout": 1000,
"pingCheckRetries": 2,
"domainName": "local",
+ "domainSearchList": [
+ "home.arpa",
+ "lan"
+ ],
+ "dnsUpdates": true,
"dnsTtl": 900,
"serverAddress": "192.168.1.1",
"serverHostName": "tftp-server-1",
@@ -4639,6 +4688,19 @@ RESPONSE:
"information": "06:01:03:0A:04:00:50:58:45:09:14:00:00:11:52:61:73:70:62:65:72:72:79:20:50:69:20:42:6F:6F:74:FF"
}
],
+ "capwapAcIpAddresses": [
+ "192.168.1.2"
+ ],
+ "tftpServerAddresses": [
+ "192.168.1.5",
+ "192.168.1.6"
+ ],
+ "genericOptions": [
+ {
+ "code": 150,
+ "value": "C0:A8:01:01"
+ }
+ ],
"exclusions": [
{
"startingAddress": "192.168.1.1",
@@ -4653,7 +4715,8 @@ RESPONSE:
"comments": "comments"
}
],
- "allowOnlyReservedLeases": false
+ "allowOnlyReservedLeases": false,
+ "blockLocallyAdministeredMacAddresses": true
},
"status": "ok"
}
@@ -4682,25 +4745,32 @@ WHERE:
- `leaseTimeDays` (optional): The lease time in number of days.
- `leaseTimeHours` (optional): The lease time in number of hours.
- `leaseTimeMinutes` (optional): The lease time in number of minutes.
-- `offerDelayTime` (optional): The time duration in milli seconds that the DHCP server delays sending an DHCPOFFER message.
+- `offerDelayTime` (optional): The time duration in milliseconds that the DHCP server delays sending an DHCPOFFER message.
- `pingCheckEnabled` (optional): Set this option to `true` to allow the DHCP server to find out if an IP address is already in use to prevent IP address conflict when some of the devices on the network have manually configured IP addresses.
- `pingCheckTimeout` (optional): The timeout interval to wait for an ping reply.
- `pingCheckRetries` (optional): The maximum number of ping requests to try.
-- `domainName` (optional): The domain name to be used by this network. The DHCP server automatically adds forward and reverse DNS entries for each IP address allocations when domain name is configured.
+- `domainName` (optional): The domain name to be used by this network. The DHCP server automatically adds forward and reverse DNS entries for each IP address allocations when domain name is configured. (Option 15)
+- `domainSearchList` (optional): A comma separated list of domain names that the clients can use as a suffix when searching a domain name. (Option 119)
+- `dnsUpdates` (optional): Set this option to `true` to allow the DHCP server to automatically update forward and reverse DNS entries for clients.
- `dnsTtl` (optional): The TTL value used for forward and reverse DNS records.
-- `serverAddress` (optional): The bootstrap TFTP server IP address to be used by the clients. If not specified, the DHCP server's IP address is used.
-- `serverHostName` (optional): The optional bootstrap TFTP server host name to be used by the clients to identify the TFTP server.
-- `bootFileName` (optional): The boot file name stored on the bootstrap TFTP server to be used by the clients.
-- `routerAddress` (optional): The default gateway or router IP address to be used by the clients.
+- `serverAddress` (optional): The IP address of next server (TFTP) to use in bootstrap by the clients. If not specified, the DHCP server's IP address is used. (siaddr)
+- `serverHostName` (optional): The optional bootstrap server host name to be used by the clients to identify the TFTP server. (sname/Option 66)
+- `bootFileName` (optional): The boot file name stored on the bootstrap TFTP server to be used by the clients. (file/Option 67)
+- `routerAddress` (optional): The default gateway IP address to be used by the clients. (Option 3)
- `useThisDnsServer` (optional): Tells the DHCP server to use this DNS server's IP address to configure the DNS Servers DHCP option for clients.
-- `dnsServers` (optional): A comma separated list of DNS server IP addresses to be used by the clients. This parameter is ignored when `useThisDnsServer` is set to `true`.
-- `winsServers` (optional): A comma separated list of NBNS/WINS server IP addresses to be used by the clients.
-- `ntpServers` (optional): A comma separated list of Network Time Protocol (NTP) server IP addresses to be used by the clients.
-- `staticRoutes` (optional): A `|` separated list of static routes in format `{destination network address}|{subnet mask}|{router/gateway address}` to be used by the clients for accessing specified destination networks.
-- `vendorInfo` (optional): A `|` separated list of vendor information in format `{vendor class identifier}|{vendor specific information}` where `{vendor specific information}` is a colon separated hex string.
+- `dnsServers` (optional): A comma separated list of DNS server IP addresses to be used by the clients. This parameter is ignored when `useThisDnsServer` is set to `true`. (Option 6)
+- `winsServers` (optional): A comma separated list of NBNS/WINS server IP addresses to be used by the clients. (Option 44)
+- `ntpServers` (optional): A comma separated list of Network Time Protocol (NTP) server IP addresses to be used by the clients. (Option 42)
+- `ntpServerDomainNames` (optional): Enter NTP server domain names (e.g. pool.ntp.org) above that the DHCP server should automatically resolve and pass the resolved IP addresses to clients as NTP server option. (Option 42)
+- `staticRoutes` (optional): A `|` separated list of static routes in format `{destination network address}|{subnet mask}|{router/gateway address}` to be used by the clients for accessing specified destination networks. (Option 121)
+- `vendorInfo` (optional): A `|` separated list of vendor information in format `{vendor class identifier}|{vendor specific information}` where `{vendor specific information}` is a colon separated hex string or a normal hex string.
+- `capwapAcIpAddresses` (optional): A comma separated list of Control And Provisioning of Wireless Access Points (CAPWAP) Access Controller IP addresses to be used by Wireless Termination Points to discover the Access Controllers to which it is to connect. (Option 138)
+- `tftpServerAddresses` (optional): A comma separated list of TFTP Server Address or the VoIP Configuration Server Address. (Option 150)
+- `genericOptions` (optional): This feature allows you to define DHCP options that are not yet directly supported. Use a `|` separated list of DHCP option code defined for it and the value in either a colon (:) separated hex string or a normal hex string in format `{option-code}|{hex-string-value}`.
- `exclusions` (optional): A `|` separated list of IP address range in format `{starting address}|{ending address}` that must be excluded or not assigned dynamically to any client by the DHCP server.
- `reservedLeases` (optional): A `|` separated list of reserved IP addresses in format `{host name}|{MAC address}|{reserved IP address}|{comments}` to be assigned to specific clients based on their MAC address.
- `allowOnlyReservedLeases` (optional): Set this parameter to `true` to stop dynamic IP address allocation and allocate only reserved IP addresses.
+- `blockLocallyAdministeredMacAddresses` (optional): Set this parameter to `true` to stop dynamic IP address allocation for clients with locally administered MAC addresses. MAC address with 0x02 bit set in the first octet indicate a locally administered MAC address which usually means that the device is not using its original MAC address.
RESPONSE:
```
@@ -4715,7 +4785,7 @@ RESPONSE:
Adds a reserved lease entry to the specified scope.
URL:\
-`http://localhost:5380/api/dhcp/scopes/addReservedLease?token=x`
+`http://localhost:5380/api/dhcp/scopes/addReservedLease?token=x&name=Default&hardwareAddress=00:00:00:00:00:00`
PERMISSIONS:\
DhcpServer: Modify
@@ -4741,7 +4811,7 @@ RESPONSE:
Removed a reserved lease entry from the specified scope.
URL:\
-`http://localhost:5380/api/dhcp/scopes/removeReservedLease?token=x`
+`http://localhost:5380/api/dhcp/scopes/removeReservedLease?token=x&name=Default&hardwareAddress=00:00:00:00:00:00`
PERMISSIONS:\
DhcpServer: Modify
@@ -5827,7 +5897,7 @@ WHERE:
- `start` (optional): The start date time in ISO 8601 format to filter the logs.
- `end` (optional): The end date time in ISO 8601 format to filter the logs.
- `clientIpAddress` (optional): The client IP address to filter the logs.
-- `protocol` (optional): The DNS transport protocol to filter the logs. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`].
+- `protocol` (optional): The DNS transport protocol to filter the logs. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `Quic`].
- `responseType` (optional): The DNS server response type to filter the logs. Valid values are [`Authoritative`, `Recursive`, `Cached`, `Blocked`].
- `rcode` (optional): The DNS response code to filter the logs.
- `qname` (optional): The query name (QNAME) in the request question section to filter the logs.
diff --git a/Apps/AdvancedBlockingApp/AdvancedBlockingApp.csproj b/Apps/AdvancedBlockingApp/AdvancedBlockingApp.csproj
index e8e33de3..6cd8b4ab 100644
--- a/Apps/AdvancedBlockingApp/AdvancedBlockingApp.csproj
+++ b/Apps/AdvancedBlockingApp/AdvancedBlockingApp.csproj
@@ -3,7 +3,7 @@
net7.0
false
- 4.0.1
+ 5.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -16,10 +16,6 @@
Library
-
-
-
-
false
@@ -27,6 +23,10 @@
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll
+ false
+
..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll
false
diff --git a/Apps/AdvancedBlockingApp/App.cs b/Apps/AdvancedBlockingApp/App.cs
index 1cd2e73d..a15781cb 100644
--- a/Apps/AdvancedBlockingApp/App.cs
+++ b/Apps/AdvancedBlockingApp/App.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
@@ -27,11 +26,14 @@ using System.Net.Http;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
+using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns;
+using TechnitiumLibrary.Net.Dns.EDnsOptions;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
namespace AdvancedBlocking
@@ -252,29 +254,21 @@ namespace AdvancedBlocking
_soaRecord = new DnsSOARecordData(_dnsServer.ServerDomain, "hostadmin@" + _dnsServer.ServerDomain, 1, 14400, 3600, 604800, 60);
_nsRecord = new DnsNSRecordData(_dnsServer.ServerDomain);
- dynamic jsonConfig = JsonConvert.DeserializeObject(config);
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
- _enableBlocking = jsonConfig.enableBlocking.Value;
- _blockListUrlUpdateIntervalHours = Convert.ToInt32(jsonConfig.blockListUrlUpdateIntervalHours.Value);
+ _enableBlocking = jsonConfig.GetProperty("enableBlocking").GetBoolean();
+ _blockListUrlUpdateIntervalHours = jsonConfig.GetProperty("blockListUrlUpdateIntervalHours").GetInt32();
+
+ _networkGroupMap = jsonConfig.ReadObjectAsMap("networkGroupMap", delegate (string network, JsonElement jsonGroup)
+ {
+ if (!NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
+ throw new InvalidOperationException("Network group map contains an invalid network address: " + network);
+
+ return new Tuple(networkAddress, jsonGroup.GetString());
+ });
{
- Dictionary networkGroupMap = new Dictionary();
-
- foreach (dynamic jsonProperty in jsonConfig.networkGroupMap)
- {
- string network = jsonProperty.Name;
- string group = jsonProperty.Value;
-
- if (NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
- networkGroupMap.Add(networkAddress, group);
- }
-
- _networkGroupMap = networkGroupMap;
- }
-
- {
- Dictionary groups = new Dictionary();
-
Dictionary allAllowListZones = new Dictionary(0);
Dictionary allBlockListZones = new Dictionary(0);
@@ -283,11 +277,9 @@ namespace AdvancedBlocking
Dictionary allAdBlockListZones = new Dictionary(0);
- foreach (dynamic jsonGroup in jsonConfig.groups)
+ _groups = jsonConfig.ReadArrayAsMap("groups", delegate (JsonElement jsonGroup)
{
Group group = new Group(this, jsonGroup);
- if (!groups.TryAdd(group.Name, group))
- continue;
foreach (Uri allowListUrl in group.AllowListUrls)
{
@@ -343,9 +335,9 @@ namespace AdvancedBlocking
allAdBlockListZones.Add(adblockListUrl, new AdBlockList(_dnsServer, adblockListUrl));
}
}
- }
- _groups = groups;
+ return new Tuple(group.Name, group);
+ });
_allAllowListZones = allAllowListZones;
_allBlockListZones = allBlockListZones;
@@ -427,10 +419,10 @@ namespace AdvancedBlocking
return Task.CompletedTask;
}
- public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
+ public async Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
{
if (!_enableBlocking)
- return Task.FromResult(null);
+ return null;
IPAddress remoteIP = remoteEP.Address;
NetworkAddress network = null;
@@ -446,37 +438,66 @@ namespace AdvancedBlocking
}
if ((groupName is null) || !_groups.TryGetValue(groupName, out Group group) || !group.EnableBlocking)
- return Task.FromResult(null);
+ return null;
DnsQuestionRecord question = request.Question[0];
- if (!group.IsZoneBlocked(question.Name, out string blockedDomain, out string blockedRegex, out Uri blockListUrl))
- return Task.FromResult(null);
-
- if (group.AllowTxtBlockingReport && (question.Type == DnsResourceRecordType.TXT))
+ if (!group.IsZoneBlocked(question.Name, out bool allowed, out string blockedDomain, out string blockedRegex, out Uri blockListUrl))
{
- //return meta data
- DnsResourceRecord[] answer;
+ if (allowed)
+ {
+ DnsDatagram internalResponse = await _dnsServer.DirectQueryAsync(request);
+ if (internalResponse.Tag is null)
+ internalResponse.Tag = DnsServerResponseType.Recursive;
+
+ return internalResponse;
+ }
+
+ return null;
+ }
+
+ string GetBlockingReport()
+ {
+ string blockingReport = "source=advanced-blocking-app; group=" + group.Name;
if (blockedRegex is null)
{
if (blockListUrl is not null)
- answer = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, 60, new DnsTXTRecordData("source=advanced-blocking-app; group=" + group.Name + "; blockListUrl=" + blockListUrl.AbsoluteUri + "; domain=" + blockedDomain)) };
+ blockingReport += "; blockListUrl=" + blockListUrl.AbsoluteUri + "; domain=" + blockedDomain;
else
- answer = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, 60, new DnsTXTRecordData("source=advanced-blocking-app; group=" + group.Name + "; domain=" + blockedDomain)) };
+ blockingReport += "; domain=" + blockedDomain;
}
else
{
if (blockListUrl is not null)
- answer = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, 60, new DnsTXTRecordData("source=advanced-blocking-app; group=" + group.Name + "; regexBlockListUrl=" + blockListUrl.AbsoluteUri + "; regex=" + blockedRegex)) };
+ blockingReport += "; regexBlockListUrl=" + blockListUrl.AbsoluteUri + "; regex=" + blockedRegex;
else
- answer = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, 60, new DnsTXTRecordData("source=advanced-blocking-app; group=" + group.Name + "; regex=" + blockedRegex)) };
+ blockingReport += "; regex=" + blockedRegex;
}
- return Task.FromResult(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answer) { Tag = DnsServerResponseType.Blocked });
+ return blockingReport;
+ }
+
+ if (group.AllowTxtBlockingReport && (question.Type == DnsResourceRecordType.TXT))
+ {
+ //return meta data
+ string blockingReport = GetBlockingReport();
+
+ DnsResourceRecord[] answer = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, 60, new DnsTXTRecordData(blockingReport)) };
+
+ return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answer) { Tag = DnsServerResponseType.Blocked };
}
else
{
+ EDnsOption[] options = null;
+
+ if (group.AllowTxtBlockingReport && (request.EDNS is not null))
+ {
+ string blockingReport = GetBlockingReport();
+
+ options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.Blocked, blockingReport)) };
+ }
+
DnsResponseCode rcode;
IReadOnlyList answer = null;
IReadOnlyList authority = null;
@@ -549,7 +570,7 @@ namespace AdvancedBlocking
}
}
- return Task.FromResult(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, rcode, request.Question, answer, authority) { Tag = DnsServerResponseType.Blocked });
+ return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, rcode, request.Question, answer, authority, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options) { Tag = DnsServerResponseType.Blocked };
}
}
@@ -600,22 +621,23 @@ namespace AdvancedBlocking
#region constructor
- public Group(App app, dynamic jsonGroup)
+ public Group(App app, JsonElement jsonGroup)
{
_app = app;
- _name = jsonGroup.name.Value;
- _enableBlocking = jsonGroup.enableBlocking.Value;
- _allowTxtBlockingReport = jsonGroup.allowTxtBlockingReport.Value;
- _blockAsNxDomain = jsonGroup.blockAsNxDomain.Value;
+ _name = jsonGroup.GetProperty("name").GetString();
+ _enableBlocking = jsonGroup.GetProperty("enableBlocking").GetBoolean();
+ _allowTxtBlockingReport = jsonGroup.GetProperty("allowTxtBlockingReport").GetBoolean();
+ _blockAsNxDomain = jsonGroup.GetProperty("blockAsNxDomain").GetBoolean();
{
+ JsonElement jsonBlockingAddresses = jsonGroup.GetProperty("blockingAddresses");
List aRecords = new List();
List aaaaRecords = new List();
- foreach (dynamic jsonBlockingAddress in jsonGroup.blockingAddresses)
+ foreach (JsonElement jsonBlockingAddress in jsonBlockingAddresses.EnumerateArray())
{
- string strAddress = jsonBlockingAddress.Value;
+ string strAddress = jsonBlockingAddress.GetString();
if (IPAddress.TryParse(strAddress, out IPAddress address))
{
@@ -636,59 +658,36 @@ namespace AdvancedBlocking
_aaaaRecords = aaaaRecords;
}
- _allowed = ReadJsonDomainArray(jsonGroup.allowed);
- _blocked = ReadJsonDomainArray(jsonGroup.blocked);
- _allowListUrls = ReadJsonUrlArray(jsonGroup.allowListUrls);
- _blockListUrls = ReadJsonUrlArray(jsonGroup.blockListUrls);
+ _allowed = jsonGroup.ReadArrayAsMap("allowed", GetMapEntry);
+ _blocked = jsonGroup.ReadArrayAsMap("blocked", GetMapEntry);
+ _allowListUrls = jsonGroup.ReadArray("allowListUrls", GetUriEntry);
+ _blockListUrls = jsonGroup.ReadArray("blockListUrls", GetUriEntry);
- _allowedRegex = ReadJsonRegexArray(jsonGroup.allowedRegex);
- _blockedRegex = ReadJsonRegexArray(jsonGroup.blockedRegex);
- _regexAllowListUrls = ReadJsonUrlArray(jsonGroup.regexAllowListUrls);
- _regexBlockListUrls = ReadJsonUrlArray(jsonGroup.regexBlockListUrls);
+ _allowedRegex = jsonGroup.ReadArray("allowedRegex", GetRegexEntry);
+ _blockedRegex = jsonGroup.ReadArray("blockedRegex", GetRegexEntry);
+ _regexAllowListUrls = jsonGroup.ReadArray("regexAllowListUrls", GetUriEntry);
+ _regexBlockListUrls = jsonGroup.ReadArray("regexBlockListUrls", GetUriEntry);
- _adblockListUrls = ReadJsonUrlArray(jsonGroup.adblockListUrls);
+ _adblockListUrls = jsonGroup.ReadArray("adblockListUrls", GetUriEntry);
}
#endregion
#region private
- private static IReadOnlyDictionary ReadJsonDomainArray(dynamic jsonDomainArray)
+ private static Tuple GetMapEntry(JsonElement jsonElement)
{
- Dictionary domains = new Dictionary(jsonDomainArray.Count);
-
- foreach (dynamic jsonDomain in jsonDomainArray)
- domains.TryAdd(jsonDomain.Value, null);
-
- return domains;
+ return new Tuple(jsonElement.GetString(), null);
}
- private static IReadOnlyList ReadJsonRegexArray(dynamic jsonRegexArray)
+ private static Uri GetUriEntry(string uriString)
{
- List regices = new List(jsonRegexArray.Count);
-
- foreach (dynamic jsonRegex in jsonRegexArray)
- {
- string regexPattern = jsonRegex.Value;
-
- regices.Add(new Regex(regexPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled));
- }
-
- return regices;
+ return new Uri(uriString);
}
- private static IReadOnlyList ReadJsonUrlArray(dynamic jsonUrlArray)
+ private static Regex GetRegexEntry(string pattern)
{
- List urls = new List(jsonUrlArray.Count);
-
- foreach (dynamic jsonUrl in jsonUrlArray)
- {
- string strUrl = jsonUrl.Value;
-
- urls.Add(new Uri(strUrl));
- }
-
- return urls;
+ return new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
}
#endregion
@@ -758,7 +757,7 @@ namespace AdvancedBlocking
}
}
- public bool IsZoneBlocked(string domain, out string blockedDomain, out string blockedRegex, out Uri listUrl)
+ public bool IsZoneBlocked(string domain, out bool allowed, out string blockedDomain, out string blockedRegex, out Uri listUrl)
{
domain = domain.ToLower();
@@ -766,6 +765,7 @@ namespace AdvancedBlocking
if (IsZoneFound(_allowed, domain, out _) || IsZoneFound(_allowListZones, domain, out _, out _) || IsMatchFound(_allowedRegex, domain, out _) || IsMatchFound(_regexAllowListZones, domain, out _, out _) || IsZoneAllowed(_adBlockListZones, domain, out _, out _))
{
//found zone allowed
+ allowed = true;
blockedDomain = null;
blockedRegex = null;
listUrl = null;
@@ -776,6 +776,7 @@ namespace AdvancedBlocking
if (IsZoneFound(_blocked, domain, out string foundZone1))
{
//found zone blocked
+ allowed = false;
blockedDomain = foundZone1;
blockedRegex = null;
listUrl = null;
@@ -786,6 +787,7 @@ namespace AdvancedBlocking
if (IsZoneFound(_blockListZones, domain, out string foundZone2, out Uri blockListUrl1))
{
//found zone blocked
+ allowed = false;
blockedDomain = foundZone2;
blockedRegex = null;
listUrl = blockListUrl1;
@@ -796,6 +798,7 @@ namespace AdvancedBlocking
if (IsMatchFound(_blockedRegex, domain, out string blockedPattern1))
{
//found pattern blocked
+ allowed = false;
blockedDomain = null;
blockedRegex = blockedPattern1;
listUrl = null;
@@ -806,6 +809,7 @@ namespace AdvancedBlocking
if (IsMatchFound(_regexBlockListZones, domain, out string blockedPattern2, out Uri blockListUrl2))
{
//found pattern blocked
+ allowed = false;
blockedDomain = null;
blockedRegex = blockedPattern2;
listUrl = blockListUrl2;
@@ -816,12 +820,14 @@ namespace AdvancedBlocking
if (App.IsZoneBlocked(_adBlockListZones, domain, out string foundZone3, out Uri blockListUrl3))
{
//found zone blocked
+ allowed = false;
blockedDomain = foundZone3;
blockedRegex = null;
listUrl = blockListUrl3;
return true;
}
+ allowed = false;
blockedDomain = null;
blockedRegex = null;
listUrl = null;
@@ -915,7 +921,7 @@ namespace AdvancedBlocking
SocketsHttpHandler handler = new SocketsHttpHandler();
handler.Proxy = _dnsServer.Proxy;
handler.UseProxy = _dnsServer.Proxy is not null;
- handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
+ handler.AutomaticDecompression = DecompressionMethods.All;
using (HttpClient http = new HttpClient(handler))
{
@@ -1035,6 +1041,8 @@ namespace AdvancedBlocking
{
#region variables
+ readonly static char[] _popWordSeperator = new char[] { ' ', '\t' };
+
IReadOnlyDictionary _listZone = new Dictionary(0);
#endregion
@@ -1054,9 +1062,9 @@ namespace AdvancedBlocking
if (line.Length == 0)
return line;
- line = line.TrimStart(' ', '\t');
+ line = line.TrimStart(_popWordSeperator);
- int i = line.IndexOfAny(new char[] { ' ', '\t' });
+ int i = line.IndexOfAny(_popWordSeperator);
string word;
if (i < 0)
@@ -1085,6 +1093,7 @@ namespace AdvancedBlocking
{
//parse hosts file and populate block zone
StreamReader sR = new StreamReader(fS, true);
+ char[] trimSeperator = new char[] { ' ', '\t', '*', '.' };
string line;
string firstWord;
string secondWord;
@@ -1096,7 +1105,7 @@ namespace AdvancedBlocking
if (line == null)
break; //eof
- line = line.TrimStart(' ', '\t');
+ line = line.TrimStart(trimSeperator);
if (line.Length == 0)
continue; //skip empty line
@@ -1216,6 +1225,7 @@ namespace AdvancedBlocking
{
//parse hosts file and populate block zone
StreamReader sR = new StreamReader(fS, true);
+ char[] trimSeperator = new char[] { ' ', '\t' };
string line;
while (true)
@@ -1224,7 +1234,7 @@ namespace AdvancedBlocking
if (line == null)
break; //eof
- line = line.TrimStart(' ', '\t');
+ line = line.TrimStart(trimSeperator);
if (line.Length == 0)
continue; //skip empty line
@@ -1314,6 +1324,7 @@ namespace AdvancedBlocking
{
//parse hosts file and populate block zone
StreamReader sR = new StreamReader(fS, true);
+ char[] trimSeperator = new char[] { ' ', '\t' };
string line;
while (true)
@@ -1322,7 +1333,7 @@ namespace AdvancedBlocking
if (line == null)
break; //eof
- line = line.TrimStart(' ', '\t');
+ line = line.TrimStart(trimSeperator);
if (line.Length == 0)
continue; //skip empty line
@@ -1358,7 +1369,7 @@ namespace AdvancedBlocking
string options = line.Substring(i + 1);
if (((options.Length == 0) || (options.StartsWith("$") && (options.Contains("doc") || options.Contains("all")))) && DnsClient.IsDomainNameValid(domain))
- blockedDomains.Enqueue(domain);
+ allowedDomains.Enqueue(domain);
}
else
{
diff --git a/Apps/AdvancedBlockingApp/dnsApp.config b/Apps/AdvancedBlockingApp/dnsApp.config
index 38d2c922..410be64e 100644
--- a/Apps/AdvancedBlockingApp/dnsApp.config
+++ b/Apps/AdvancedBlockingApp/dnsApp.config
@@ -10,7 +10,7 @@
"name": "everyone",
"enableBlocking": true,
"allowTxtBlockingReport": true,
- "blockAsNxDomain": false,
+ "blockAsNxDomain": true,
"blockingAddresses": [
"0.0.0.0",
"::"
@@ -35,7 +35,7 @@
"name": "kids",
"enableBlocking": true,
"allowTxtBlockingReport": true,
- "blockAsNxDomain": false,
+ "blockAsNxDomain": true,
"blockingAddresses": [
"0.0.0.0",
"::"
diff --git a/Apps/AdvancedForwardingApp/AdvancedForwardingApp.csproj b/Apps/AdvancedForwardingApp/AdvancedForwardingApp.csproj
new file mode 100644
index 00000000..c923bc47
--- /dev/null
+++ b/Apps/AdvancedForwardingApp/AdvancedForwardingApp.csproj
@@ -0,0 +1,45 @@
+
+
+
+ net7.0
+ false
+ 1.0
+ Technitium
+ Technitium DNS Server
+ Shreyas Zare
+ AdvancedForwardingApp
+ AdvancedForwarding
+ https://technitium.com/dns/
+ https://github.com/TechnitiumSoftware/DnsServer
+ Provides advanced, bulk conditional forwarding options. Supports creating groups based on client's IP address or subnet to enable different conditional forwarding configuration for each group. Supports AdGuard Upstreams config files.\n\nNote: This app works independent of the DNS server's built-in Conditional Forwarder Zones feature.
+ false
+ Library
+
+
+
+
+ false
+
+
+
+
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll
+ false
+
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll
+ false
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/Apps/AdvancedForwardingApp/App.cs b/Apps/AdvancedForwardingApp/App.cs
new file mode 100644
index 00000000..267fc0e4
--- /dev/null
+++ b/Apps/AdvancedForwardingApp/App.cs
@@ -0,0 +1,808 @@
+/*
+Technitium DNS Server
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using DnsServerCore.ApplicationCommon;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using TechnitiumLibrary;
+using TechnitiumLibrary.Net;
+using TechnitiumLibrary.Net.Dns;
+using TechnitiumLibrary.Net.Dns.ResourceRecords;
+using TechnitiumLibrary.Net.Proxy;
+
+namespace AdvancedForwarding
+{
+ public class App : IDnsApplication, IDnsAuthoritativeRequestHandler
+ {
+ #region variables
+
+ IDnsServer _dnsServer;
+
+ bool _enableForwarding;
+ Dictionary _configProxyServers;
+ Dictionary _configForwarders;
+ IReadOnlyDictionary _networkGroupMap;
+ IReadOnlyDictionary _groups;
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_groups is not null)
+ {
+ foreach (KeyValuePair group in _groups)
+ group.Value.Dispose();
+ }
+ }
+
+ #endregion
+
+ #region private
+
+ private static IReadOnlyList GetUpdatedForwarderRecords(IReadOnlyList forwarderRecords, bool dnssecValidation, ConfigProxyServer configProxyServer)
+ {
+ List newForwarderRecords = new List(forwarderRecords.Count);
+
+ foreach (DnsForwarderRecordData forwarderRecord in forwarderRecords)
+ newForwarderRecords.Add(GetForwarderRecord(forwarderRecord.Protocol, forwarderRecord.Forwarder, dnssecValidation, configProxyServer));
+
+ return newForwarderRecords;
+ }
+
+ private static DnsForwarderRecordData GetForwarderRecord(NameServerAddress forwarder, bool dnssecValidation, ConfigProxyServer configProxyServer)
+ {
+ return GetForwarderRecord(forwarder.Protocol, forwarder.ToString(), dnssecValidation, configProxyServer);
+ }
+
+ private static DnsForwarderRecordData GetForwarderRecord(DnsTransportProtocol protocol, string forwarder, bool dnssecValidation, ConfigProxyServer configProxyServer)
+ {
+ DnsForwarderRecordData forwarderRecord;
+
+ if (configProxyServer is null)
+ forwarderRecord = new DnsForwarderRecordData(protocol, forwarder, dnssecValidation, NetProxyType.None, null, 0, null, null);
+ else
+ forwarderRecord = new DnsForwarderRecordData(protocol, forwarder, dnssecValidation, configProxyServer.Type, configProxyServer.ProxyAddress, configProxyServer.ProxyPort, configProxyServer.ProxyUsername, configProxyServer.ProxyPassword);
+
+ return forwarderRecord;
+ }
+
+ private Tuple ReadGroup(JsonElement jsonGroup)
+ {
+ Group group;
+ string name = jsonGroup.GetProperty("name").GetString();
+
+ if ((_groups is not null) && _groups.TryGetValue(name, out group))
+ group.ReloadConfig(_configProxyServers, _configForwarders, jsonGroup);
+ else
+ group = new Group(_dnsServer, _configProxyServers, _configForwarders, jsonGroup);
+
+ return new Tuple(group.Name, group);
+ }
+
+ #endregion
+
+ #region public
+
+ public Task InitializeAsync(IDnsServer dnsServer, string config)
+ {
+ _dnsServer = dnsServer;
+
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
+
+ _enableForwarding = jsonConfig.GetPropertyValue("enableForwarding", true);
+
+ if (jsonConfig.TryReadArrayAsMap("proxyServers", delegate (JsonElement jsonProxy)
+ {
+ ConfigProxyServer proxyServer = new ConfigProxyServer(jsonProxy);
+ return new Tuple(proxyServer.Name, proxyServer);
+ }, out Dictionary configProxyServers))
+ _configProxyServers = configProxyServers;
+ else
+ _configProxyServers = null;
+
+ if (jsonConfig.TryReadArrayAsMap("forwarders", delegate (JsonElement jsonForwarder)
+ {
+ ConfigForwarder forwarder = new ConfigForwarder(jsonForwarder, _configProxyServers);
+ return new Tuple(forwarder.Name, forwarder);
+ }, out Dictionary configForwarders))
+ _configForwarders = configForwarders;
+ else
+ _configForwarders = null;
+
+ _networkGroupMap = jsonConfig.ReadObjectAsMap("networkGroupMap", delegate (string network, JsonElement jsonGroup)
+ {
+ if (!NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
+ throw new FormatException("Network group map contains an invalid network address: " + network);
+
+ return new Tuple(networkAddress, jsonGroup.GetString());
+ });
+
+ if (jsonConfig.TryReadArrayAsMap("groups", ReadGroup, out Dictionary groups))
+ {
+ if (_groups is not null)
+ {
+ foreach (KeyValuePair group in _groups)
+ {
+ if (!groups.ContainsKey(group.Key))
+ group.Value.Dispose();
+ }
+ }
+
+ _groups = groups;
+ }
+ else
+ {
+ throw new FormatException("Groups array was not defined.");
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
+ {
+ if (!_enableForwarding)
+ return Task.FromResult(null);
+
+ IPAddress remoteIP = remoteEP.Address;
+ NetworkAddress network = null;
+ string groupName = null;
+
+ foreach (KeyValuePair entry in _networkGroupMap)
+ {
+ if (entry.Key.Contains(remoteIP) && ((network is null) || (entry.Key.PrefixLength > network.PrefixLength)))
+ {
+ network = entry.Key;
+ groupName = entry.Value;
+ }
+ }
+
+ if ((groupName is null) || !_groups.TryGetValue(groupName, out Group group) || !group.EnableForwarding)
+ return Task.FromResult(null);
+
+ DnsQuestionRecord question = request.Question[0];
+ string qname = question.Name;
+
+ if (!group.TryGetForwarderRecords(qname, out IReadOnlyList forwarderRecords))
+ return Task.FromResult(null);
+
+ DnsResourceRecord[] authority = new DnsResourceRecord[forwarderRecords.Count];
+
+ for (int i = 0; i < forwarderRecords.Count; i++)
+ authority[i] = new DnsResourceRecord(qname, DnsResourceRecordType.FWD, DnsClass.IN, 0, forwarderRecords[i]);
+
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority));
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Description
+ { get { return "Performs bulk conditional forwarding for configured domain names and AdGuard Upstream config files."; } }
+
+ #endregion
+
+ class Group : IDisposable
+ {
+ #region variables
+
+ readonly IDnsServer _dnsServer;
+ Dictionary _configProxyServers;
+ Dictionary _configForwarders;
+
+ readonly string _name;
+ bool _enableForwarding;
+ IReadOnlyList _forwardings;
+ IReadOnlyDictionary _adguardUpstreams;
+
+ #endregion
+
+ #region constructor
+
+ public Group(IDnsServer dnsServer, Dictionary configProxyServers, Dictionary configForwarders, JsonElement jsonGroup)
+ {
+ _dnsServer = dnsServer;
+
+ _name = jsonGroup.GetProperty("name").GetString();
+
+ ReloadConfig(configProxyServers, configForwarders, jsonGroup);
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_adguardUpstreams is not null)
+ {
+ foreach (KeyValuePair adguardUpstream in _adguardUpstreams)
+ adguardUpstream.Value.Dispose();
+
+ _adguardUpstreams = null;
+ }
+ }
+
+ #endregion
+
+ #region private
+
+ private Tuple ReadAdGuardUpstream(JsonElement jsonAdguardUpstream)
+ {
+ AdGuardUpstream adGuardUpstream;
+ string name = jsonAdguardUpstream.GetProperty("configFile").GetString();
+
+ if ((_adguardUpstreams is not null) && _adguardUpstreams.TryGetValue(name, out adGuardUpstream))
+ adGuardUpstream.ReloadConfig(_configProxyServers, jsonAdguardUpstream);
+ else
+ adGuardUpstream = new AdGuardUpstream(_dnsServer, _configProxyServers, jsonAdguardUpstream);
+
+ return new Tuple(adGuardUpstream.Name, adGuardUpstream);
+ }
+
+ #endregion
+
+ #region public
+
+ public void ReloadConfig(Dictionary configProxyServers, Dictionary configForwarders, JsonElement jsonGroup)
+ {
+ _configProxyServers = configProxyServers;
+ _configForwarders = configForwarders;
+
+ _enableForwarding = jsonGroup.GetPropertyValue("enableForwarding", true);
+
+ if (jsonGroup.TryReadArray("forwardings", delegate (JsonElement jsonForwarding) { return new Forwarding(jsonForwarding, _configForwarders); }, out Forwarding[] forwardings))
+ _forwardings = forwardings;
+ else
+ _forwardings = null;
+
+ if (jsonGroup.TryReadArrayAsMap("adguardUpstreams", ReadAdGuardUpstream, out Dictionary adguardUpstreams))
+ {
+ if (_adguardUpstreams is not null)
+ {
+ foreach (KeyValuePair adguardUpstream in _adguardUpstreams)
+ {
+ if (!adguardUpstreams.ContainsKey(adguardUpstream.Key))
+ adguardUpstream.Value.Dispose();
+ }
+ }
+
+ _adguardUpstreams = adguardUpstreams;
+ }
+ else
+ {
+ if (_adguardUpstreams is not null)
+ {
+ foreach (KeyValuePair adguardUpstream in _adguardUpstreams)
+ adguardUpstream.Value.Dispose();
+ }
+
+ _adguardUpstreams = null;
+ }
+ }
+
+ public bool TryGetForwarderRecords(string domain, out IReadOnlyList forwarderRecords)
+ {
+ domain = domain.ToLower();
+
+ if ((_forwardings is not null) && (_forwardings.Count > 0) && Forwarding.TryGetForwarderRecords(domain, _forwardings, out forwarderRecords))
+ return true;
+
+ if (_adguardUpstreams is not null)
+ {
+ foreach (KeyValuePair adguardUpstream in _adguardUpstreams)
+ {
+ if (adguardUpstream.Value.TryGetForwarderRecords(domain, out forwarderRecords))
+ return true;
+ }
+ }
+
+ forwarderRecords = null;
+ return false;
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Name
+ { get { return _name; } }
+
+ public bool EnableForwarding
+ { get { return _enableForwarding; } }
+
+ #endregion
+ }
+
+ class Forwarding
+ {
+ #region variables
+
+ IReadOnlyList _forwarderRecords;
+ readonly IReadOnlyDictionary _domainMap;
+
+ #endregion
+
+ #region constructor
+
+ public Forwarding(JsonElement jsonForwarding, Dictionary configForwarders)
+ {
+ JsonElement jsonForwarders = jsonForwarding.GetProperty("forwarders");
+ List forwarderRecords = new List();
+
+ foreach (JsonElement jsonForwarder in jsonForwarders.EnumerateArray())
+ {
+ string forwarderName = jsonForwarder.GetString();
+
+ if ((configForwarders is null) || !configForwarders.TryGetValue(forwarderName, out ConfigForwarder configForwarder))
+ throw new FormatException("Forwarder was not defined: " + forwarderName);
+
+ forwarderRecords.AddRange(configForwarder.ForwarderRecords);
+ }
+
+ _forwarderRecords = forwarderRecords;
+
+ _domainMap = jsonForwarding.ReadArrayAsMap("domains", delegate (JsonElement jsonDomain)
+ {
+ return new Tuple(jsonDomain.GetString().ToLower(), null);
+ });
+ }
+
+ public Forwarding(IReadOnlyList domains, NameServerAddress forwarder, bool dnssecValidation, ConfigProxyServer proxy)
+ : this(new DnsForwarderRecordData[] { GetForwarderRecord(forwarder, dnssecValidation, proxy) }, domains)
+ { }
+
+ public Forwarding(IReadOnlyList forwarderRecords, IReadOnlyList domains)
+ {
+ _forwarderRecords = forwarderRecords;
+
+ Dictionary domainMap = new Dictionary(domains.Count);
+
+ foreach (string domain in domains)
+ {
+ if (DnsClient.IsDomainNameValid(domain))
+ domainMap.TryAdd(domain.ToLower(), null);
+ }
+
+ _domainMap = domainMap;
+ }
+
+ #endregion
+
+ #region static
+
+ public static bool TryGetForwarderRecords(string domain, IReadOnlyList forwardings, out IReadOnlyList forwarderRecords)
+ {
+ if (forwardings.Count == 1)
+ {
+ if (forwardings[0].TryGetForwarderRecords(domain, out forwarderRecords, out _))
+ return true;
+ }
+ else
+ {
+ Dictionary> fwdMap = new Dictionary>(forwardings.Count);
+
+ foreach (Forwarding forwarding in forwardings)
+ {
+ if (forwarding.TryGetForwarderRecords(domain, out IReadOnlyList fwdRecords, out string matchedDomain))
+ {
+ if (fwdMap.TryGetValue(matchedDomain, out List fwdRecordsList))
+ {
+ fwdRecordsList.AddRange(fwdRecords);
+ }
+ else
+ {
+ fwdRecordsList = new List(fwdRecords);
+ fwdMap.Add(matchedDomain, fwdRecordsList);
+ }
+ }
+ }
+
+ if (fwdMap.Count > 0)
+ {
+ forwarderRecords = null;
+ string lastMatchedDomain = null;
+
+ foreach (KeyValuePair> fwdEntry in fwdMap)
+ {
+ if ((lastMatchedDomain is null) || (fwdEntry.Key.Length > lastMatchedDomain.Length) || ((fwdEntry.Key.Length == lastMatchedDomain.Length) && lastMatchedDomain.StartsWith("*.")))
+ {
+ lastMatchedDomain = fwdEntry.Key;
+ forwarderRecords = fwdEntry.Value;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ forwarderRecords = null;
+ return false;
+ }
+
+ #endregion
+
+ #region private
+
+ private static string GetParentZone(string domain)
+ {
+ int i = domain.IndexOf('.');
+ if (i > -1)
+ return domain.Substring(i + 1);
+
+ //dont return root zone
+ return null;
+ }
+
+ private bool IsDomainBlocked(string domain, out string matchedDomain)
+ {
+ string parent;
+
+ do
+ {
+ if (_domainMap.TryGetValue(domain, out _))
+ {
+ matchedDomain = domain;
+ return true;
+ }
+
+ parent = GetParentZone(domain);
+ if (parent is null)
+ break;
+
+ domain = "*." + parent;
+
+ if (_domainMap.TryGetValue(domain, out _))
+ {
+ matchedDomain = domain;
+ return true;
+ }
+
+ domain = parent;
+ }
+ while (true);
+
+ matchedDomain = null;
+ return false;
+ }
+
+ private bool TryGetForwarderRecords(string domain, out IReadOnlyList forwarderRecords, out string matchedDomain)
+ {
+ if (IsDomainBlocked(domain, out matchedDomain))
+ {
+ forwarderRecords = _forwarderRecords;
+ return true;
+ }
+
+ forwarderRecords = null;
+ return false;
+ }
+
+ #endregion
+
+ #region public
+
+ public void UpdateForwarderRecords(bool dnssecValidation, ConfigProxyServer proxy)
+ {
+ _forwarderRecords = GetUpdatedForwarderRecords(_forwarderRecords, dnssecValidation, proxy);
+ }
+
+ #endregion
+ }
+
+ class AdGuardUpstream : IDisposable
+ {
+ #region variables
+
+ readonly IDnsServer _dnsServer;
+
+ readonly string _name;
+ ConfigProxyServer _configProxyServer;
+ bool _dnssecValidation;
+
+ IReadOnlyList _defaultForwarderRecords;
+ IReadOnlyList _forwardings;
+
+ readonly string _configFile;
+ DateTime _configFileLastModified;
+
+ Timer _autoReloadTimer;
+ const int AUTO_RELOAD_TIMER_INTERVAL = 60000;
+
+ #endregion
+
+ #region constructor
+
+ public AdGuardUpstream(IDnsServer dnsServer, Dictionary configProxyServers, JsonElement jsonAdguardUpstream)
+ {
+ _dnsServer = dnsServer;
+
+ _name = jsonAdguardUpstream.GetProperty("configFile").GetString();
+
+ _configFile = _name;
+
+ if (!Path.IsPathRooted(_configFile))
+ _configFile = Path.Combine(_dnsServer.ApplicationFolder, _configFile);
+
+ _autoReloadTimer = new Timer(delegate (object state)
+ {
+ try
+ {
+ DateTime configFileLastModified = File.GetLastWriteTimeUtc(_configFile);
+ if (configFileLastModified > _configFileLastModified)
+ ReloadUpstreamsFile();
+ }
+ catch (Exception ex)
+ {
+ _dnsServer.WriteLog(ex);
+ }
+ finally
+ {
+ _autoReloadTimer?.Change(AUTO_RELOAD_TIMER_INTERVAL, Timeout.Infinite);
+ }
+ });
+
+ ReloadConfig(configProxyServers, jsonAdguardUpstream);
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_autoReloadTimer is not null)
+ {
+ _autoReloadTimer.Dispose();
+ _autoReloadTimer = null;
+ }
+ }
+
+ #endregion
+
+ #region private
+
+ private void ReloadUpstreamsFile()
+ {
+ try
+ {
+ _dnsServer.WriteLog("The app is reading AdGuard Upstreams config file: " + _configFile);
+
+ List defaultForwarderRecords = new List();
+ List forwardings = new List();
+
+ using (FileStream fS = new FileStream(_configFile, FileMode.Open, FileAccess.Read))
+ {
+ StreamReader sR = new StreamReader(fS, true);
+ string line;
+
+ while (true)
+ {
+ line = sR.ReadLine();
+ if (line is null)
+ break; //eof
+
+ line = line.TrimStart();
+
+ if (line.Length == 0)
+ continue; //skip empty line
+
+ if (line.StartsWith("#"))
+ continue; //skip comment line
+
+ if (line.StartsWith("["))
+ {
+ int i = line.LastIndexOf(']');
+ if (i < 0)
+ throw new FormatException("Invalid AdGuard Upstreams config file format: missing ']' bracket.");
+
+ string[] domains = line.Substring(1, i - 1).Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ string forwarder = line.Substring(i + 1);
+
+ if (forwarder == "#")
+ {
+ if (defaultForwarderRecords.Count == 0)
+ throw new FormatException("Invalid AdGuard Upstreams config file format: missing default upstream servers.");
+
+ forwardings.Add(new Forwarding(defaultForwarderRecords, domains));
+ }
+ else
+ {
+ forwardings.Add(new Forwarding(domains, NameServerAddress.Parse(forwarder), _dnssecValidation, _configProxyServer));
+ }
+ }
+ else
+ {
+ defaultForwarderRecords.Add(GetForwarderRecord(NameServerAddress.Parse(line), _dnssecValidation, _configProxyServer));
+ }
+ }
+
+ _configFileLastModified = File.GetLastWriteTimeUtc(fS.SafeFileHandle);
+ }
+
+ _defaultForwarderRecords = defaultForwarderRecords;
+ _forwardings = forwardings;
+
+ _dnsServer.WriteLog("The app has successfully loaded AdGuard Upstreams config file: " + _configFile);
+ }
+ catch (Exception ex)
+ {
+ _dnsServer.WriteLog("The app failed to read AdGuard Upstreams config file: " + _configFile + "\r\n" + ex.ToString());
+ }
+ }
+
+ #endregion
+
+ #region public
+
+ public void ReloadConfig(Dictionary configProxyServers, JsonElement jsonAdguardUpstream)
+ {
+ string proxyName = jsonAdguardUpstream.GetPropertyValue("proxy", null);
+ _dnssecValidation = jsonAdguardUpstream.GetPropertyValue("dnssecValidation", true);
+
+ ConfigProxyServer configProxyServer = null;
+
+ if (!string.IsNullOrEmpty(proxyName) && ((configProxyServers is null) || !configProxyServers.TryGetValue(proxyName, out configProxyServer)))
+ throw new FormatException("Proxy server was not defined: " + proxyName);
+
+ _configProxyServer = configProxyServer;
+
+ DateTime configFileLastModified = File.GetLastWriteTimeUtc(_configFile);
+ if (configFileLastModified > _configFileLastModified)
+ {
+ //reload complete config file
+ _autoReloadTimer.Change(0, Timeout.Infinite);
+ }
+ else
+ {
+ //update only forwarder records
+ _defaultForwarderRecords = GetUpdatedForwarderRecords(_defaultForwarderRecords, _dnssecValidation, _configProxyServer);
+
+ foreach (Forwarding forwarding in _forwardings)
+ forwarding.UpdateForwarderRecords(_dnssecValidation, _configProxyServer);
+ }
+ }
+
+ public bool TryGetForwarderRecords(string domain, out IReadOnlyList forwarderRecords)
+ {
+ if ((_forwardings.Count > 0) && Forwarding.TryGetForwarderRecords(domain, _forwardings, out forwarderRecords))
+ return true;
+
+ if (_defaultForwarderRecords.Count > 0)
+ {
+ forwarderRecords = _defaultForwarderRecords;
+ return true;
+ }
+
+ forwarderRecords = null;
+ return false;
+ }
+
+ #endregion
+
+ #region property
+
+ public string Name
+ { get { return _name; } }
+
+ #endregion
+ }
+
+ class ConfigProxyServer
+ {
+ #region variables
+
+ readonly string _name;
+ readonly NetProxyType _type;
+ readonly string _proxyAddress;
+ readonly ushort _proxyPort;
+ readonly string _proxyUsername;
+ readonly string _proxyPassword;
+
+ #endregion
+
+ #region constructor
+
+ public ConfigProxyServer(JsonElement jsonProxy)
+ {
+ _name = jsonProxy.GetProperty("name").GetString();
+ _type = jsonProxy.GetPropertyEnumValue("type", NetProxyType.Http);
+ _proxyAddress = jsonProxy.GetProperty("proxyAddress").GetString();
+ _proxyPort = jsonProxy.GetProperty("proxyPort").GetUInt16();
+ _proxyUsername = jsonProxy.GetPropertyValue("proxyUsername", null);
+ _proxyPassword = jsonProxy.GetPropertyValue("proxyPassword", null);
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Name
+ { get { return _name; } }
+
+ public NetProxyType Type
+ { get { return _type; } }
+
+ public string ProxyAddress
+ { get { return _proxyAddress; } }
+
+ public ushort ProxyPort
+ { get { return _proxyPort; } }
+
+ public string ProxyUsername
+ { get { return _proxyUsername; } }
+
+ public string ProxyPassword
+ { get { return _proxyPassword; } }
+
+ #endregion
+ }
+
+ class ConfigForwarder
+ {
+ #region variables
+
+ readonly string _name;
+ readonly IReadOnlyList _forwarderRecords;
+
+ #endregion
+
+ #region constructor
+
+ public ConfigForwarder(JsonElement jsonForwarder, Dictionary configProxyServers)
+ {
+ _name = jsonForwarder.GetProperty("name").GetString();
+
+ string proxyName = jsonForwarder.GetPropertyValue("proxy", null);
+ bool dnssecValidation = jsonForwarder.GetPropertyValue("dnssecValidation", true);
+ DnsTransportProtocol forwarderProtocol = jsonForwarder.GetPropertyEnumValue("forwarderProtocol", DnsTransportProtocol.Udp);
+
+ ConfigProxyServer configProxyServer = null;
+
+ if (!string.IsNullOrEmpty(proxyName) && ((configProxyServers is null) || !configProxyServers.TryGetValue(proxyName, out configProxyServer)))
+ throw new FormatException("Proxy server was not defined: " + proxyName);
+
+ _forwarderRecords = jsonForwarder.ReadArray("forwarderAddresses", delegate (string address)
+ {
+ return GetForwarderRecord(forwarderProtocol, address, dnssecValidation, configProxyServer);
+ });
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Name
+ { get { return _name; } }
+
+ public IReadOnlyList ForwarderRecords
+ { get { return _forwarderRecords; } }
+
+ #endregion
+ }
+ }
+}
diff --git a/Apps/AdvancedForwardingApp/adguard-upstreams.txt b/Apps/AdvancedForwardingApp/adguard-upstreams.txt
new file mode 100644
index 00000000..77674a51
--- /dev/null
+++ b/Apps/AdvancedForwardingApp/adguard-upstreams.txt
@@ -0,0 +1,10 @@
+# AdGuard Upstreams
+# File Format Reference: https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams
+#
+# Example:
+# 8.8.8.8
+# udp://9.9.9.9
+# [/host.com/example.com/]tls://1.1.1.1
+# [/maps.host.com/]#
+# [/home/]192.168.10.2
+# [/test.com/]https://dns.quad9.net/dns-query (9.9.9.9)
diff --git a/Apps/AdvancedForwardingApp/dnsApp.config b/Apps/AdvancedForwardingApp/dnsApp.config
new file mode 100644
index 00000000..40479b5e
--- /dev/null
+++ b/Apps/AdvancedForwardingApp/dnsApp.config
@@ -0,0 +1,77 @@
+{
+ "enableForwarding": true,
+ "proxyServers": [
+ {
+ "name": "local-proxy",
+ "type": "socks5",
+ "proxyAddress": "localhost",
+ "proxyPort": 1080,
+ "proxyUsername": null,
+ "proxyPassword": null
+ }
+ ],
+ "forwarders": [
+ {
+ "name": "quad9-doh",
+ "proxy": null,
+ "dnssecValidation": true,
+ "forwarderProtocol": "Https",
+ "forwarderAddresses": [
+ "https://dns.quad9.net/dns-query (9.9.9.9)"
+ ]
+ },
+ {
+ "name": "cloudflare-google",
+ "proxy": null,
+ "dnssecValidation": true,
+ "forwarderProtocol": "Tls",
+ "forwarderAddresses": [
+ "1.1.1.1",
+ "8.8.8.8"
+ ]
+ },
+ {
+ "name": "quad9-tls-proxied",
+ "proxy": "local-proxy",
+ "dnssecValidation": true,
+ "forwarderProtocol": "Tls",
+ "forwarderAddresses": [
+ "9.9.9.9"
+ ]
+ }
+ ],
+ "networkGroupMap": {
+ "0.0.0.0/0": "everyone"
+ },
+ "groups": [
+ {
+ "name": "everyone",
+ "enableForwarding": true,
+ "forwardings": [
+ {
+ "forwarders": [
+ "quad9-doh"
+ ],
+ "domains": [
+ "example.com"
+ ]
+ },
+ {
+ "forwarders": [
+ "cloudflare-google"
+ ],
+ "domains": [
+ "example.net"
+ ]
+ }
+ ],
+ "adguardUpstreams": [
+ {
+ "proxy": null,
+ "dnssecValidation": true,
+ "configFile": "adguard-upstreams.txt"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Apps/BlockPageApp/App.cs b/Apps/BlockPageApp/App.cs
index cf4c8581..1b9a912d 100644
--- a/Apps/BlockPageApp/App.cs
+++ b/Apps/BlockPageApp/App.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
@@ -28,6 +27,7 @@ using System.Net.Sockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using TechnitiumLibrary;
@@ -296,13 +296,17 @@ namespace BlockPage
if (usingHttps)
{
SslStream httpsStream = new SslStream(stream);
- await httpsStream.AuthenticateAsServerAsync(_webServerTlsCertificate);
+ await httpsStream.AuthenticateAsServerAsync(_webServerTlsCertificate).WithTimeout(TCP_RECV_TIMEOUT);
stream = httpsStream;
}
await ProcessHttpRequestAsync(stream, remoteEP, usingHttps);
}
+ catch (TimeoutException)
+ {
+ //ignore timeout exception on TLS auth
+ }
catch (IOException)
{
//ignore IO exceptions
@@ -313,8 +317,7 @@ namespace BlockPage
}
finally
{
- if (socket is not null)
- socket.Dispose();
+ socket.Dispose();
}
}
@@ -479,35 +482,29 @@ namespace BlockPage
{
_dnsServer = dnsServer;
- dynamic jsonConfig = JsonConvert.DeserializeObject(config);
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
- {
- List webServerLocalAddresses = new List();
+ _webServerLocalAddresses = jsonConfig.ReadArray("webServerLocalAddresses", IPAddress.Parse);
- foreach (dynamic jsonAddress in jsonConfig.webServerLocalAddresses)
- webServerLocalAddresses.Add(IPAddress.Parse(jsonAddress.Value));
-
- _webServerLocalAddresses = webServerLocalAddresses;
- }
-
- if (jsonConfig.webServerUseSelfSignedTlsCertificate is null)
- _webServerUseSelfSignedTlsCertificate = true;
+ if (jsonConfig.TryGetProperty("webServerUseSelfSignedTlsCertificate", out JsonElement jsonWebServerUseSelfSignedTlsCertificate))
+ _webServerUseSelfSignedTlsCertificate = jsonWebServerUseSelfSignedTlsCertificate.GetBoolean();
else
- _webServerUseSelfSignedTlsCertificate = jsonConfig.webServerUseSelfSignedTlsCertificate.Value;
+ _webServerUseSelfSignedTlsCertificate = true;
- _webServerTlsCertificateFilePath = jsonConfig.webServerTlsCertificateFilePath.Value;
- _webServerTlsCertificatePassword = jsonConfig.webServerTlsCertificatePassword.Value;
+ _webServerTlsCertificateFilePath = jsonConfig.GetProperty("webServerTlsCertificateFilePath").GetString();
+ _webServerTlsCertificatePassword = jsonConfig.GetProperty("webServerTlsCertificatePassword").GetString();
- _webServerRootPath = jsonConfig.webServerRootPath.Value;
+ _webServerRootPath = jsonConfig.GetProperty("webServerRootPath").GetString();
if (!Path.IsPathRooted(_webServerRootPath))
_webServerRootPath = Path.Combine(_dnsServer.ApplicationFolder, _webServerRootPath);
- _serveBlockPageFromWebServerRoot = jsonConfig.serveBlockPageFromWebServerRoot.Value;
+ _serveBlockPageFromWebServerRoot = jsonConfig.GetProperty("serveBlockPageFromWebServerRoot").GetBoolean();
- string blockPageTitle = jsonConfig.blockPageTitle.Value;
- string blockPageHeading = jsonConfig.blockPageHeading.Value;
- string blockPageMessage = jsonConfig.blockPageMessage.Value;
+ string blockPageTitle = jsonConfig.GetProperty("blockPageTitle").GetString();
+ string blockPageHeading = jsonConfig.GetProperty("blockPageHeading").GetString();
+ string blockPageMessage = jsonConfig.GetProperty("blockPageMessage").GetString();
string blockPageContent = @"
@@ -570,7 +567,7 @@ namespace BlockPage
_dnsServer.WriteLog(ex);
}
- if (jsonConfig.webServerUseSelfSignedTlsCertificate is null)
+ if (!jsonConfig.TryGetProperty("webServerUseSelfSignedTlsCertificate", out _))
{
config = config.Replace("\"webServerTlsCertificateFilePath\"", "\"webServerUseSelfSignedTlsCertificate\": true,\r\n \"webServerTlsCertificateFilePath\"");
diff --git a/Apps/BlockPageApp/BlockPageApp.csproj b/Apps/BlockPageApp/BlockPageApp.csproj
index eb68d450..8992c987 100644
--- a/Apps/BlockPageApp/BlockPageApp.csproj
+++ b/Apps/BlockPageApp/BlockPageApp.csproj
@@ -3,7 +3,7 @@
net7.0
false
- 3.0.1
+ 4.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -26,10 +26,6 @@
-
-
-
-
false
diff --git a/Apps/Dns64App/App.cs b/Apps/Dns64App/App.cs
index 39c9f682..8d2b0dae 100644
--- a/Apps/Dns64App/App.cs
+++ b/Apps/Dns64App/App.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,12 +18,13 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
+using System.Text.Json;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
@@ -60,41 +61,27 @@ namespace Dns64
{
_dnsServer = dnsServer;
- dynamic jsonConfig = JsonConvert.DeserializeObject(config);
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
- _enableDns64 = jsonConfig.enableDns64.Value;
+ _enableDns64 = jsonConfig.GetProperty("enableDns64").GetBoolean();
+ _networkGroupMap = jsonConfig.ReadObjectAsMap("networkGroupMap", delegate (string network, JsonElement group)
{
- Dictionary networkGroupMap = new Dictionary();
+ if (!NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
+ throw new InvalidOperationException("Network group map contains an invalid network address: " + network);
- foreach (dynamic jsonProperty in jsonConfig.networkGroupMap)
- {
- string network = jsonProperty.Name;
- string group = jsonProperty.Value;
+ if (networkAddress.Address.AddressFamily == AddressFamily.InterNetwork)
+ throw new InvalidOperationException("Network group map can only have IPv6 network addresses: " + network);
- if (!NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
- throw new InvalidOperationException("Network group map contains an invalid network address: " + network);
-
- if (networkAddress.Address.AddressFamily == AddressFamily.InterNetwork)
- throw new InvalidOperationException("Network group map can only have IPv6 network addresses: " + network);
-
- networkGroupMap.Add(networkAddress, group);
- }
-
- _networkGroupMap = networkGroupMap;
- }
+ return new Tuple(networkAddress, group.GetString());
+ });
+ _groups = jsonConfig.ReadArrayAsMap("groups", delegate (JsonElement jsonGroup)
{
- Dictionary groups = new Dictionary();
-
- foreach (dynamic jsonGroup in jsonConfig.groups)
- {
- Group group = new Group(jsonGroup);
- groups.Add(group.Name, group);
- }
-
- _groups = groups;
- }
+ Group group = new Group(jsonGroup);
+ return new Tuple(group.Name, group);
+ });
return Task.CompletedTask;
}
@@ -161,13 +148,13 @@ namespace Dns64
if (!synthesizeAAAA)
return new DnsDatagram(response.Identifier, true, response.OPCODE, response.AuthoritativeAnswer, response.Truncation, response.RecursionDesired, response.RecursionAvailable, response.AuthenticData, response.CheckingDisabled, response.RCODE, response.Question, newAnswer, response.Authority, response.Additional) { Tag = response.Tag };
- DnsDatagram newResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(question.Name, DnsResourceRecordType.A, question.Class), 2000);
+ DnsDatagram newResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN), 2000);
uint soaTtl;
{
DnsResourceRecord soa = response.FindFirstAuthorityRecord();
if ((soa is not null) && (soa.Type == DnsResourceRecordType.SOA))
- soaTtl = soa.TtlValue;
+ soaTtl = soa.TTL;
else
soaTtl = 600;
}
@@ -195,7 +182,7 @@ namespace Dns64
IPAddress ipv6Address = ipv4Address.MapToIPv6(dns64Prefix);
- newAnswer.Add(new DnsResourceRecord(answer.Name, DnsResourceRecordType.AAAA, answer.Class, Math.Min(answer.TtlValue, soaTtl), new DnsAAAARecordData(ipv6Address)));
+ newAnswer.Add(new DnsResourceRecord(answer.Name, DnsResourceRecordType.AAAA, answer.Class, Math.Min(answer.TTL, soaTtl), new DnsAAAARecordData(ipv6Address)));
}
return new DnsDatagram(response.Identifier, true, response.OPCODE, response.AuthoritativeAnswer, response.Truncation, response.RecursionDesired, response.RecursionAvailable, response.AuthenticData, response.CheckingDisabled, newResponse.RCODE, response.Question, newAnswer, newResponse.Authority, newResponse.Additional) { Tag = response.Tag };
@@ -229,7 +216,7 @@ namespace Dns64
if ((groupName is null) || !_groups.TryGetValue(groupName, out Group group) || !group.EnableDns64)
return Task.FromResult(null);
- IPAddress ipv6Address = IPAddressExtension.ParseReverseDomain(question.Name);
+ IPAddress ipv6Address = IPAddressExtensions.ParseReverseDomain(question.Name);
if (ipv6Address.AddressFamily != AddressFamily.InterNetworkV6)
return Task.FromResult(null);
@@ -275,61 +262,48 @@ namespace Dns64
#region constructor
- public Group(dynamic jsonGroup)
+ public Group(JsonElement jsonGroup)
{
- _name = jsonGroup.name.Value;
- _enableDns64 = jsonGroup.enableDns64.Value;
+ _name = jsonGroup.GetProperty("name").GetString();
+ _enableDns64 = jsonGroup.GetProperty("enableDns64").GetBoolean();
+ _dns64PrefixMap = jsonGroup.ReadObjectAsMap("dns64PrefixMap", delegate (string strNetwork, JsonElement jsonDns64Prefix)
{
- Dictionary dns64PrefixMap = new Dictionary();
+ string strDns64Prefix = jsonDns64Prefix.GetString();
- foreach (dynamic jsonProperty in jsonGroup.dns64PrefixMap)
+ NetworkAddress network = NetworkAddress.Parse(strNetwork);
+ NetworkAddress dns64Prefix = null;
+
+ if (strDns64Prefix is not null)
{
- string strNetwork = jsonProperty.Name;
- string strDns64Prefix = jsonProperty.Value;
+ dns64Prefix = NetworkAddress.Parse(strDns64Prefix);
- NetworkAddress network = NetworkAddress.Parse(strNetwork);
- NetworkAddress dns64Prefix = null;
-
- if (strDns64Prefix is not null)
+ switch (dns64Prefix.PrefixLength)
{
- dns64Prefix = NetworkAddress.Parse(strDns64Prefix);
+ case 32:
+ case 40:
+ case 48:
+ case 56:
+ case 64:
+ case 96:
+ break;
- switch (dns64Prefix.PrefixLength)
- {
- case 32:
- case 40:
- case 48:
- case 56:
- case 64:
- case 96:
- break;
-
- default:
- throw new NotSupportedException("DNS64 prefix can have only the following prefixes: 32, 40, 48, 56, 64, or 96.");
- }
+ default:
+ throw new NotSupportedException("DNS64 prefix can have only the following prefixes: 32, 40, 48, 56, 64, or 96.");
}
-
- dns64PrefixMap.Add(network, dns64Prefix);
}
- _dns64PrefixMap = dns64PrefixMap;
- }
+ return new Tuple(network, dns64Prefix);
+ });
+ _excludedIpv6 = jsonGroup.ReadArray("excludedIpv6", delegate (string strNetworkAddress)
{
- List excludedIpv6 = new List();
+ NetworkAddress networkAddress = NetworkAddress.Parse(strNetworkAddress);
+ if (networkAddress.Address.AddressFamily != AddressFamily.InterNetworkV6)
+ throw new InvalidOperationException("An IPv6 network address is expected for 'excludedIpv6' array.");
- foreach (dynamic jsonItem in jsonGroup.excludedIpv6)
- {
- NetworkAddress networkAddress = NetworkAddress.Parse(jsonItem.Value);
- if (networkAddress.Address.AddressFamily != AddressFamily.InterNetworkV6)
- throw new InvalidOperationException("An IPv6 network address is expected for 'excludedIpv6' array.");
-
- excludedIpv6.Add(networkAddress);
- }
-
- _excludedIpv6 = excludedIpv6;
- }
+ return networkAddress;
+ });
}
#endregion
diff --git a/Apps/Dns64App/Dns64App.csproj b/Apps/Dns64App/Dns64App.csproj
index 967db31d..4195166c 100644
--- a/Apps/Dns64App/Dns64App.csproj
+++ b/Apps/Dns64App/Dns64App.csproj
@@ -3,7 +3,7 @@
net7.0
false
- 1.0.1
+ 2.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -16,10 +16,6 @@
Library
-
-
-
-
false
@@ -27,6 +23,10 @@
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll
+ false
+
..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll
false
diff --git a/Apps/DnsBlockListApp/App.cs b/Apps/DnsBlockListApp/App.cs
new file mode 100644
index 00000000..d1d13e55
--- /dev/null
+++ b/Apps/DnsBlockListApp/App.cs
@@ -0,0 +1,813 @@
+/*
+Technitium DNS Server
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using DnsServerCore.ApplicationCommon;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using TechnitiumLibrary;
+using TechnitiumLibrary.Net;
+using TechnitiumLibrary.Net.Dns;
+using TechnitiumLibrary.Net.Dns.ResourceRecords;
+
+namespace DnsBlockList
+{
+ //DNS Blacklists and Whitelists
+ //https://www.rfc-editor.org/rfc/rfc5782
+
+ public class App : IDnsApplication, IDnsAppRecordRequestHandler
+ {
+ #region variables
+
+ IDnsServer _dnsServer;
+
+ IReadOnlyDictionary _dnsBlockLists;
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_dnsBlockLists is not null)
+ {
+ foreach (KeyValuePair dnsBlockList in _dnsBlockLists)
+ dnsBlockList.Value.Dispose();
+
+ _dnsBlockLists = null;
+ }
+ }
+
+ #endregion
+
+ #region private
+
+ private static bool TryParseDnsblDomain(string qName, string appRecordName, out IPAddress address, out string domain)
+ {
+ qName = qName.Substring(0, qName.Length - appRecordName.Length - 1);
+
+ string[] parts = qName.Split('.');
+ string lastPart = parts[parts.Length - 1];
+
+ if (byte.TryParse(lastPart, out _) || byte.TryParse(lastPart, NumberStyles.HexNumber, null, out _))
+ {
+ switch (parts.Length)
+ {
+ case 4:
+ {
+ byte[] buffer = new byte[4];
+
+ for (int i = 0, j = parts.Length - 1; (i < 4) && (j > -1); i++, j--)
+ buffer[i] = byte.Parse(parts[j]);
+
+ address = new IPAddress(buffer);
+ domain = null;
+ return true;
+ }
+
+ case 32:
+ {
+ byte[] buffer = new byte[16];
+
+ for (int i = 0, j = parts.Length - 1; (i < 16) && (j > 0); i++, j -= 2)
+ buffer[i] = (byte)(byte.Parse(parts[j], NumberStyles.HexNumber) << 4 | byte.Parse(parts[j - 1], NumberStyles.HexNumber));
+
+ address = new IPAddress(buffer);
+ domain = null;
+ return true;
+ }
+
+ default:
+ address = null;
+ domain = null;
+ return false;
+ }
+ }
+ else
+ {
+ address = null;
+ domain = lastPart;
+
+ for (int i = parts.Length - 2; i > -1; i--)
+ domain = parts[i] + "." + domain;
+
+ return true;
+ }
+ }
+
+ private Tuple ReadBlockList(JsonElement jsonBlockList)
+ {
+ BlockList blockList;
+ string name = jsonBlockList.GetProperty("name").GetString();
+ BlockListType type = jsonBlockList.GetPropertyEnumValue("type", BlockListType.Ip);
+
+ if ((_dnsBlockLists is not null) && _dnsBlockLists.TryGetValue(name, out BlockList existingBlockList) && (existingBlockList.Type == type))
+ {
+ existingBlockList.ReloadConfig(jsonBlockList);
+ blockList = existingBlockList;
+ }
+ else
+ {
+ switch (type)
+ {
+ case BlockListType.Ip:
+ blockList = new IpBlockList(_dnsServer, jsonBlockList);
+ break;
+
+ case BlockListType.Domain:
+ blockList = new DomainBlockList(_dnsServer, jsonBlockList);
+ break;
+
+ default:
+ throw new NotSupportedException("DNSBL block list type is not supported: " + type.ToString());
+ }
+ }
+
+ return new Tuple(blockList.Name, blockList);
+ }
+
+ #endregion
+
+ #region public
+
+ public Task InitializeAsync(IDnsServer dnsServer, string config)
+ {
+ _dnsServer = dnsServer;
+
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
+
+ if (jsonConfig.TryReadArrayAsMap("dnsBlockLists", ReadBlockList, out Dictionary dnsBlockLists))
+ {
+ if (_dnsBlockLists is not null)
+ {
+ foreach (KeyValuePair dnsBlockList in _dnsBlockLists)
+ {
+ if (!dnsBlockLists.ContainsKey(dnsBlockList.Key))
+ dnsBlockList.Value.Dispose();
+ }
+ }
+
+ _dnsBlockLists = dnsBlockLists;
+ }
+ else
+ {
+ if (_dnsBlockLists is not null)
+ {
+ foreach (KeyValuePair dnsBlockList in _dnsBlockLists)
+ dnsBlockList.Value.Dispose();
+ }
+
+ _dnsBlockLists = null;
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public async Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
+ {
+ DnsQuestionRecord question = request.Question[0];
+ string qname = question.Name;
+
+ if (qname.Length == appRecordName.Length)
+ return null;
+
+ if ((_dnsBlockLists is null) || !TryParseDnsblDomain(qname, appRecordName, out IPAddress address, out string domain))
+ return null;
+
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+
+ if (jsonAppRecordData.TryReadArray("dnsBlockLists", out string[] dnsBlockLists))
+ {
+ bool isBlocked = false;
+ IPAddress responseA = null;
+ string responseTXT = null;
+
+ if (address is not null)
+ {
+ foreach (string dnsBlockList in dnsBlockLists)
+ {
+ if (_dnsBlockLists.TryGetValue(dnsBlockList, out BlockList blockList) && blockList.Enabled && (blockList.Type == BlockListType.Ip) && blockList.IsBlocked(address, out responseA, out responseTXT))
+ {
+ isBlocked = true;
+
+ if (!string.IsNullOrEmpty(responseTXT))
+ responseTXT = responseTXT.Replace("{ip}", address.ToString());
+
+ break;
+ }
+ }
+ }
+ else if (domain is not null)
+ {
+ foreach (string dnsBlockList in dnsBlockLists)
+ {
+ if (_dnsBlockLists.TryGetValue(dnsBlockList, out BlockList blockList) && blockList.Enabled && (blockList.Type == BlockListType.Domain) && blockList.IsBlocked(domain, out string foundDomain, out responseA, out responseTXT))
+ {
+ isBlocked = true;
+
+ if (!string.IsNullOrEmpty(responseTXT))
+ responseTXT = responseTXT.Replace("{domain}", foundDomain);
+
+ break;
+ }
+ }
+ }
+
+ if (isBlocked)
+ {
+ switch (question.Type)
+ {
+ case DnsResourceRecordType.A:
+ return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { new DnsResourceRecord(qname, DnsResourceRecordType.A, question.Class, appRecordTtl, new DnsARecordData(responseA)) });
+
+ case DnsResourceRecordType.TXT:
+ if (!string.IsNullOrEmpty(responseTXT))
+ return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { new DnsResourceRecord(qname, DnsResourceRecordType.TXT, question.Class, appRecordTtl, new DnsTXTRecordData(responseTXT)) });
+
+ break;
+ }
+
+ //NODATA response
+ DnsDatagram soaResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(zoneName, DnsResourceRecordType.SOA, DnsClass.IN));
+
+ return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, soaResponse.Answer);
+ }
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Description
+ { get { return "Returns A or TXT records based on the DNS Block Lists (DNSBL) configured in the APP record data. Returns NXDOMAIN response when an IP address or domain name is not blocked in any of the configured blocklists."; } }
+
+ public string ApplicationRecordDataTemplate
+ {
+ get
+ {
+ return @"{
+ ""dnsBlockLists"": [
+ ""ipblocklist1"",
+ ""domainblocklist1""
+ ]
+}";
+ }
+ }
+
+ #endregion
+
+ enum BlockListType
+ {
+ Ip = 1,
+ Domain = 2
+ }
+
+ abstract class BlockList : IDisposable
+ {
+ #region variables
+
+ protected static readonly char[] _popWordSeperator = new char[] { ' ', '\t', '|' };
+
+ protected readonly IDnsServer _dnsServer;
+ readonly BlockListType _type;
+
+ readonly string _name;
+ bool _enabled;
+ protected IPAddress _responseA;
+ protected string _responseTXT;
+ protected string _blockListFile;
+
+ protected DateTime _blockListFileLastModified;
+
+ Timer _autoReloadTimer;
+ const int AUTO_RELOAD_TIMER_INTERVAL = 60000;
+
+ #endregion
+
+ #region constructor
+
+ protected BlockList(IDnsServer dnsServer, BlockListType type, JsonElement jsonBlockList)
+ {
+ _dnsServer = dnsServer;
+ _type = type;
+
+ _name = jsonBlockList.GetProperty("name").GetString();
+
+ _autoReloadTimer = new Timer(delegate (object state)
+ {
+ try
+ {
+ DateTime blockListFileLastModified = File.GetLastWriteTimeUtc(_blockListFile);
+ if (blockListFileLastModified > _blockListFileLastModified)
+ ReloadBlockListFile();
+ }
+ catch (Exception ex)
+ {
+ _dnsServer.WriteLog(ex);
+ }
+ finally
+ {
+ _autoReloadTimer?.Change(AUTO_RELOAD_TIMER_INTERVAL, Timeout.Infinite);
+ }
+ });
+
+ ReloadConfig(jsonBlockList);
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_autoReloadTimer is not null)
+ {
+ _autoReloadTimer.Dispose();
+ _autoReloadTimer = null;
+ }
+ }
+
+ #endregion
+
+ #region protected
+
+ protected abstract void ReloadBlockListFile();
+
+ protected static string PopWord(ref string line)
+ {
+ if (line.Length == 0)
+ return line;
+
+ line = line.TrimStart(_popWordSeperator);
+
+ int i = line.IndexOfAny(_popWordSeperator);
+ string word;
+
+ if (i < 0)
+ {
+ word = line;
+ line = "";
+ }
+ else
+ {
+ word = line.Substring(0, i);
+ line = line.Substring(i + 1);
+ }
+
+ return word;
+ }
+
+ #endregion
+
+ #region public
+
+ public void ReloadConfig(JsonElement jsonBlockList)
+ {
+ _enabled = jsonBlockList.GetPropertyValue("enabled", true);
+ _responseA = IPAddress.Parse(jsonBlockList.GetPropertyValue("responseA", "127.0.0.2"));
+
+ if (jsonBlockList.TryGetProperty("responseTXT", out JsonElement jsonResponseTXT))
+ _responseTXT = jsonResponseTXT.GetString();
+ else
+ _responseTXT = null;
+
+ string blockListFile = jsonBlockList.GetProperty("blockListFile").GetString();
+
+ if (!Path.IsPathRooted(blockListFile))
+ blockListFile = Path.Combine(_dnsServer.ApplicationFolder, blockListFile);
+
+ if (!blockListFile.Equals(_blockListFile))
+ {
+ _blockListFile = blockListFile;
+ _blockListFileLastModified = default;
+ }
+
+ _autoReloadTimer.Change(0, Timeout.Infinite);
+ }
+
+ public virtual bool IsBlocked(IPAddress address, out IPAddress responseA, out string responseTXT)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public virtual bool IsBlocked(string domain, out string foundDomain, out IPAddress responseA, out string responseTXT)
+ {
+ throw new InvalidOperationException();
+ }
+
+ #endregion
+
+ #region properties
+
+ public BlockListType Type
+ { get { return _type; } }
+
+ public string Name
+ { get { return _name; } }
+
+ public bool Enabled
+ { get { return _enabled; } }
+
+ public IPAddress ResponseA
+ { get { return _responseA; } }
+
+ public string ResponseTXT
+ { get { return _responseTXT; } }
+
+ public string BlockListFile
+ { get { return _blockListFile; } }
+
+ #endregion
+ }
+
+ class BlockEntry
+ {
+ #region variables
+
+ readonly T _key;
+ readonly IPAddress _responseA;
+ readonly string _responseTXT;
+
+ #endregion
+
+ #region constructor
+
+ public BlockEntry(T key, string responseA, string responseTXT)
+ {
+ _key = key;
+
+ if (IPAddress.TryParse(responseA, out IPAddress addr))
+ _responseA = addr;
+
+ if (!string.IsNullOrEmpty(responseTXT))
+ _responseTXT = responseTXT;
+ }
+
+ #endregion
+
+ #region properties
+
+ public T Key
+ { get { return _key; } }
+
+ public IPAddress ResponseA
+ { get { return _responseA; } }
+
+ public string ResponseTXT
+ { get { return _responseTXT; } }
+
+ #endregion
+ }
+
+ class IpBlockList : BlockList
+ {
+ #region variables
+
+ Dictionary> _ipv4Map;
+ Dictionary> _ipv6Map;
+ NetworkMap> _ipv4NetworkMap;
+ NetworkMap> _ipv6NetworkMap;
+
+ #endregion
+
+ #region constructor
+
+ public IpBlockList(IDnsServer dnsServer, JsonElement jsonBlockList)
+ : base(dnsServer, BlockListType.Ip, jsonBlockList)
+ { }
+
+ #endregion
+
+ #region protected
+
+ protected override void ReloadBlockListFile()
+ {
+ try
+ {
+ _dnsServer.WriteLog("The app is reading IP block list file: " + _blockListFile);
+
+ //parse ip block list file
+ Queue> ipv4Addresses = new Queue>();
+ Queue> ipv6Addresses = new Queue>();
+ Queue> ipv4Networks = new Queue>();
+ Queue> ipv6Networks = new Queue>();
+
+ ipv4Addresses.Enqueue(new BlockEntry(IPAddress.Parse("127.0.0.2"), "127.0.0.2", "rfc5782 test entry"));
+ ipv6Addresses.Enqueue(new BlockEntry(IPAddress.Parse("::FFFF:7F00:2"), "127.0.0.2", "rfc5782 test entry"));
+
+ using (FileStream fS = new FileStream(_blockListFile, FileMode.Open, FileAccess.Read))
+ {
+ StreamReader sR = new StreamReader(fS, true);
+ string line;
+ string network;
+ string responseA;
+ string responseTXT;
+
+ while (true)
+ {
+ line = sR.ReadLine();
+ if (line is null)
+ break; //eof
+
+ line = line.TrimStart(_popWordSeperator);
+
+ if (line.Length == 0)
+ continue; //skip empty line
+
+ if (line.StartsWith("#"))
+ continue; //skip comment line
+
+ network = PopWord(ref line);
+ responseA = PopWord(ref line);
+ responseTXT = line;
+
+ if (NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
+ {
+ switch (networkAddress.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ if (networkAddress.PrefixLength == 32)
+ ipv4Addresses.Enqueue(new BlockEntry(networkAddress.Address, responseA, responseTXT));
+ else
+ ipv4Networks.Enqueue(new BlockEntry(networkAddress, responseA, responseTXT));
+
+ break;
+
+ case AddressFamily.InterNetworkV6:
+ if (networkAddress.PrefixLength == 128)
+ ipv6Addresses.Enqueue(new BlockEntry(networkAddress.Address, responseA, responseTXT));
+ else
+ ipv6Networks.Enqueue(new BlockEntry(networkAddress, responseA, responseTXT));
+
+ break;
+ }
+ }
+ }
+
+ _blockListFileLastModified = File.GetLastWriteTimeUtc(fS.SafeFileHandle);
+ }
+
+ //load ip lookup list
+ Dictionary> ipv4AddressMap = new Dictionary>(ipv4Addresses.Count);
+
+ while (ipv4Addresses.Count > 0)
+ {
+ BlockEntry entry = ipv4Addresses.Dequeue();
+ ipv4AddressMap.TryAdd(entry.Key, entry);
+ }
+
+ Dictionary> ipv6AddressMap = new Dictionary>(ipv6Addresses.Count);
+
+ while (ipv6Addresses.Count > 0)
+ {
+ BlockEntry entry = ipv6Addresses.Dequeue();
+ ipv6AddressMap.TryAdd(entry.Key, entry);
+ }
+
+ NetworkMap> ipv4NetworkMap = new NetworkMap>(ipv4Networks.Count);
+
+ while (ipv4Networks.Count > 0)
+ {
+ BlockEntry entry = ipv4Networks.Dequeue();
+ ipv4NetworkMap.Add(entry.Key, entry);
+ }
+
+ NetworkMap> ipv6NetworkMap = new NetworkMap>(ipv6Networks.Count);
+
+ while (ipv6Networks.Count > 0)
+ {
+ BlockEntry entry = ipv6Networks.Dequeue();
+ ipv6NetworkMap.Add(entry.Key, entry);
+ }
+
+ //update
+ _ipv4Map = ipv4AddressMap;
+ _ipv6Map = ipv6AddressMap;
+ _ipv4NetworkMap = ipv4NetworkMap;
+ _ipv6NetworkMap = ipv6NetworkMap;
+
+ _dnsServer.WriteLog("The app has successfully loaded IP block list file: " + _blockListFile);
+ }
+ catch (Exception ex)
+ {
+ _dnsServer.WriteLog("The app failed to read IP block list file: " + _blockListFile + "\r\n" + ex.ToString());
+ }
+ }
+
+ #endregion
+
+ #region public
+
+ public override bool IsBlocked(IPAddress address, out IPAddress responseA, out string responseTXT)
+ {
+ switch (address.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ {
+ if (_ipv4Map.TryGetValue(address, out BlockEntry ipEntry))
+ {
+ responseA = ipEntry.ResponseA is null ? _responseA : ipEntry.ResponseA;
+ responseTXT = ipEntry.ResponseTXT is null ? _responseTXT : ipEntry.ResponseTXT;
+ return true;
+ }
+
+ if (_ipv4NetworkMap.TryGetValue(address, out BlockEntry networkEntry))
+ {
+ responseA = networkEntry.ResponseA is null ? _responseA : networkEntry.ResponseA;
+ responseTXT = networkEntry.ResponseTXT is null ? _responseTXT : networkEntry.ResponseTXT;
+ return true;
+ }
+ }
+ break;
+
+ case AddressFamily.InterNetworkV6:
+ {
+ if (_ipv6Map.TryGetValue(address, out BlockEntry ipEntry))
+ {
+ responseA = ipEntry.ResponseA is null ? _responseA : ipEntry.ResponseA;
+ responseTXT = ipEntry.ResponseTXT is null ? _responseTXT : ipEntry.ResponseTXT;
+ return true;
+ }
+
+ if (_ipv6NetworkMap.TryGetValue(address, out BlockEntry networkEntry))
+ {
+ responseA = networkEntry.ResponseA is null ? _responseA : networkEntry.ResponseA;
+ responseTXT = networkEntry.ResponseTXT is null ? _responseTXT : networkEntry.ResponseTXT;
+ return true;
+ }
+ }
+ break;
+ }
+
+ responseA = null;
+ responseTXT = null;
+ return false;
+ }
+
+ #endregion
+ }
+
+ class DomainBlockList : BlockList
+ {
+ #region variables
+
+ Dictionary> _domainMap;
+
+ #endregion
+
+ #region constructor
+
+ public DomainBlockList(IDnsServer dnsServer, JsonElement jsonIpBlockList)
+ : base(dnsServer, BlockListType.Domain, jsonIpBlockList)
+ { }
+
+ #endregion
+
+ #region protected
+
+ protected override void ReloadBlockListFile()
+ {
+ try
+ {
+ _dnsServer.WriteLog("The app is reading domain block list file: " + _blockListFile);
+
+ //parse ip block list file
+ Queue> domains = new Queue>();
+
+ domains.Enqueue(new BlockEntry("test", "127.0.0.2", "rfc5782 test entry"));
+
+ using (FileStream fS = new FileStream(_blockListFile, FileMode.Open, FileAccess.Read))
+ {
+ StreamReader sR = new StreamReader(fS, true);
+ char[] trimSeperator = new char[] { ' ', '\t', ':', '|', ',' };
+ string line;
+ string domain;
+ string responseA;
+ string responseTXT;
+
+ while (true)
+ {
+ line = sR.ReadLine();
+ if (line is null)
+ break; //eof
+
+ line = line.TrimStart(trimSeperator);
+
+ if (line.Length == 0)
+ continue; //skip empty line
+
+ if (line.StartsWith("#"))
+ continue; //skip comment line
+
+ domain = PopWord(ref line);
+ responseA = PopWord(ref line);
+ responseTXT = line;
+
+ if (DnsClient.IsDomainNameValid(domain))
+ domains.Enqueue(new BlockEntry(domain.ToLower(), responseA, responseTXT));
+ }
+
+ _blockListFileLastModified = File.GetLastWriteTimeUtc(fS.SafeFileHandle);
+ }
+
+ //load ip lookup list
+ Dictionary> domainMap = new Dictionary>(domains.Count);
+
+ while (domains.Count > 0)
+ {
+ BlockEntry entry = domains.Dequeue();
+ domainMap.TryAdd(entry.Key, entry);
+ }
+
+ //update
+ _domainMap = domainMap;
+
+ _dnsServer.WriteLog("The app has successfully loaded domain block list file: " + _blockListFile);
+ }
+ catch (Exception ex)
+ {
+ _dnsServer.WriteLog("The app failed to read domain block list file: " + _blockListFile + "\r\n" + ex.ToString());
+ }
+ }
+
+ #endregion
+
+ #region private
+
+ private static string GetParentZone(string domain)
+ {
+ int i = domain.IndexOf('.');
+ if (i > -1)
+ return domain.Substring(i + 1);
+
+ //dont return root zone
+ return null;
+ }
+
+ private bool IsDomainBlocked(string domain, out BlockEntry domainEntry)
+ {
+ do
+ {
+ if (_domainMap.TryGetValue(domain, out domainEntry))
+ {
+ return true;
+ }
+
+ domain = GetParentZone(domain);
+ }
+ while (domain is not null);
+
+ return false;
+ }
+
+ #endregion
+
+ #region public
+
+ public override bool IsBlocked(string domain, out string foundDomain, out IPAddress responseA, out string responseTXT)
+ {
+ if (IsDomainBlocked(domain.ToLower(), out BlockEntry domainEntry))
+ {
+ foundDomain = domainEntry.Key;
+ responseA = domainEntry.ResponseA is null ? _responseA : domainEntry.ResponseA;
+ responseTXT = domainEntry.ResponseTXT is null ? _responseTXT : domainEntry.ResponseTXT;
+ return true;
+ }
+
+ foundDomain = null;
+ responseA = null;
+ responseTXT = null;
+ return false;
+ }
+
+ #endregion
+ }
+ }
+}
diff --git a/Apps/DnsBlockListApp/DnsBlockListApp.csproj b/Apps/DnsBlockListApp/DnsBlockListApp.csproj
new file mode 100644
index 00000000..02c9578a
--- /dev/null
+++ b/Apps/DnsBlockListApp/DnsBlockListApp.csproj
@@ -0,0 +1,48 @@
+
+
+
+ net7.0
+ false
+ 1.0
+ Technitium
+ Technitium DNS Server
+ Shreyas Zare
+ DnsBlockListApp
+ DnsBlockList
+ https://technitium.com/dns/
+ https://github.com/TechnitiumSoftware/DnsServer
+ Allows creating APP records in primary and forwarder zones that can return A or TXT records based on the DNS Block Lists (DNSBL) configured. The implementation is based on RFC 5782.
+ false
+ Library
+
+
+
+
+ false
+
+
+
+
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll
+ false
+
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll
+ false
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/Apps/DnsBlockListApp/dnsApp.config b/Apps/DnsBlockListApp/dnsApp.config
new file mode 100644
index 00000000..0a58cb61
--- /dev/null
+++ b/Apps/DnsBlockListApp/dnsApp.config
@@ -0,0 +1,20 @@
+{
+ "dnsBlockLists": [
+ {
+ "name": "ipblocklist1",
+ "type": "ip",
+ "enabled": true,
+ "responseA": "127.0.0.2",
+ "responseTXT": "https://example.com/dnsbl?ip={ip}",
+ "blockListFile": "ip-blocklist.txt"
+ },
+ {
+ "name": "domainblocklist1",
+ "type": "domain",
+ "enabled": true,
+ "responseA": "127.0.0.2",
+ "responseTXT": "https://example.com/dnsbl?domain={domain}",
+ "blockListFile": "domain-blocklist.txt"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Apps/DnsBlockListApp/domain-blocklist.txt b/Apps/DnsBlockListApp/domain-blocklist.txt
new file mode 100644
index 00000000..f7fc3007
--- /dev/null
+++ b/Apps/DnsBlockListApp/domain-blocklist.txt
@@ -0,0 +1,10 @@
+# DNSBL domain block list
+# Format: domain A-response TXT-response
+# Seperator: , , or char
+#
+# A-response & TXT-response are optional but A-response must exists when TXT-response is specified
+#
+# Examples:
+# example.com
+# example.net 127.0.0.4
+# malware.com 127.0.0.4 malware see: https://example.com/dnsbl?domain={domain}
diff --git a/Apps/DnsBlockListApp/ip-blocklist.txt b/Apps/DnsBlockListApp/ip-blocklist.txt
new file mode 100644
index 00000000..6da722d1
--- /dev/null
+++ b/Apps/DnsBlockListApp/ip-blocklist.txt
@@ -0,0 +1,13 @@
+# DNSBL IP block list
+# Format: ip/network A-response TXT-response
+# Seperator: , , or char
+#
+# A-response & TXT-response are optional but A-response must exists when TXT-response is specified.
+# Supports both IPv4 and IPv6 addresses.
+#
+# Examples:
+# 192.168.1.1
+# 192.168.0.0/24
+# 192.168.2.1 127.0.0.3
+# 10.8.1.0/24 127.0.0.3 malware see: https://example.com/dnsbl?ip={ip}
+# 2001:db8::/64
diff --git a/Apps/DropRequestsApp/App.cs b/Apps/DropRequestsApp/App.cs
index 724bbbf6..a542f27e 100644
--- a/Apps/DropRequestsApp/App.cs
+++ b/Apps/DropRequestsApp/App.cs
@@ -18,12 +18,13 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
@@ -55,64 +56,32 @@ namespace DropRequests
public async Task InitializeAsync(IDnsServer dnsServer, string config)
{
- dynamic jsonConfig = JsonConvert.DeserializeObject(config);
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
- _enableBlocking = jsonConfig.enableBlocking.Value;
+ _enableBlocking = jsonConfig.GetProperty("enableBlocking").GetBoolean();
- if (jsonConfig.dropMalformedRequests is null)
+ if (jsonConfig.TryGetProperty("dropMalformedRequests", out JsonElement jsonDropMalformedRequests))
+ _dropMalformedRequests = jsonDropMalformedRequests.GetBoolean();
+ else
_dropMalformedRequests = false;
- else
- _dropMalformedRequests = jsonConfig.dropMalformedRequests.Value;
-
- if (jsonConfig.allowedNetworks is null)
- {
- _allowedNetworks = Array.Empty();
- }
- else
- {
- List allowedNetworks = new List();
-
- foreach (dynamic allowedNetwork in jsonConfig.allowedNetworks)
- {
- allowedNetworks.Add(NetworkAddress.Parse(allowedNetwork.Value));
- }
+ if (jsonConfig.TryReadArray("allowedNetworks", NetworkAddress.Parse, out NetworkAddress[] allowedNetworks))
_allowedNetworks = allowedNetworks;
- }
-
- if (jsonConfig.blockedNetworks is null)
- {
- _blockedNetworks = Array.Empty();
- }
else
- {
- List blockedNetworks = new List();
-
- foreach (dynamic blockedNetwork in jsonConfig.blockedNetworks)
- {
- blockedNetworks.Add(NetworkAddress.Parse(blockedNetwork.Value));
- }
+ _allowedNetworks = Array.Empty();
+ if (jsonConfig.TryReadArray("blockedNetworks", NetworkAddress.Parse, out NetworkAddress[] blockedNetworks))
_blockedNetworks = blockedNetworks;
- }
-
- if (jsonConfig.blockedQuestions is null)
- {
- _blockedQuestions = Array.Empty();
- }
else
- {
- List blockedQuestions = new List();
-
- foreach (dynamic blockedQuestion in jsonConfig.blockedQuestions)
- {
- blockedQuestions.Add(new BlockedQuestion(blockedQuestion));
- }
+ _blockedNetworks = Array.Empty();
+ if (jsonConfig.TryReadArray("blockedQuestions", delegate (JsonElement blockedQuestion) { return new BlockedQuestion(blockedQuestion); }, out BlockedQuestion[] blockedQuestions))
_blockedQuestions = blockedQuestions;
- }
+ else
+ _blockedQuestions = Array.Empty();
- if (jsonConfig.dropMalformedRequests is null)
+ if (!jsonConfig.TryGetProperty("dropMalformedRequests", out _))
{
config = config.Replace("\"allowedNetworks\"", "\"dropMalformedRequests\": false,\r\n \"allowedNetworks\"");
@@ -177,17 +146,15 @@ namespace DropRequests
#region constructor
- public BlockedQuestion(dynamic jsonQuestion)
+ public BlockedQuestion(JsonElement jsonQuestion)
{
- _name = jsonQuestion.name?.Value;
- if (_name is not null)
- _name = _name.TrimEnd('.');
+ if (jsonQuestion.TryGetProperty("name", out JsonElement jsonName))
+ _name = jsonName.GetString().TrimEnd('.');
- if (jsonQuestion.blockZone is not null)
- _blockZone = jsonQuestion.blockZone.Value;
+ if (jsonQuestion.TryGetProperty("blockZone", out JsonElement jsonBlockZone))
+ _blockZone = jsonBlockZone.GetBoolean();
- string strType = jsonQuestion.type?.Value;
- if (!string.IsNullOrEmpty(strType) && Enum.TryParse(strType, true, out DnsResourceRecordType type))
+ if (jsonQuestion.TryGetProperty("type", out JsonElement jsonType) && Enum.TryParse(jsonType.GetString(), true, out DnsResourceRecordType type))
_type = type;
else
_type = DnsResourceRecordType.Unknown;
diff --git a/Apps/DropRequestsApp/DropRequestsApp.csproj b/Apps/DropRequestsApp/DropRequestsApp.csproj
index 859d7b39..546d56c5 100644
--- a/Apps/DropRequestsApp/DropRequestsApp.csproj
+++ b/Apps/DropRequestsApp/DropRequestsApp.csproj
@@ -3,7 +3,7 @@
net7.0
false
- 3.0.1
+ 4.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -16,10 +16,6 @@
Library
-
-
-
-
false
@@ -27,6 +23,10 @@
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll
+ false
+
..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll
false
diff --git a/Apps/FailoverApp/Address.cs b/Apps/FailoverApp/Address.cs
index cda6b414..38d873cb 100644
--- a/Apps/FailoverApp/Address.cs
+++ b/Apps/FailoverApp/Address.cs
@@ -18,11 +18,11 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns;
@@ -64,17 +64,14 @@ namespace Failover
#region private
- private void GetAnswers(dynamic jsonAddresses, DnsQuestionRecord question, uint appRecordTtl, string healthCheck, Uri healthCheckUrl, List answers)
+ private void GetAnswers(JsonElement jsonAddresses, DnsQuestionRecord question, uint appRecordTtl, string healthCheck, Uri healthCheckUrl, List answers)
{
- if (jsonAddresses == null)
- return;
-
switch (question.Type)
{
case DnsResourceRecordType.A:
- foreach (dynamic jsonAddress in jsonAddresses)
+ foreach (JsonElement jsonAddress in jsonAddresses.EnumerateArray())
{
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
if (address.AddressFamily == AddressFamily.InterNetwork)
{
@@ -94,9 +91,9 @@ namespace Failover
break;
case DnsResourceRecordType.AAAA:
- foreach (dynamic jsonAddress in jsonAddresses)
+ foreach (JsonElement jsonAddress in jsonAddresses.EnumerateArray())
{
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
@@ -117,14 +114,11 @@ namespace Failover
}
}
- private void GetStatusAnswers(dynamic jsonAddresses, FailoverType type, DnsQuestionRecord question, uint appRecordTtl, string healthCheck, Uri healthCheckUrl, List answers)
+ private void GetStatusAnswers(JsonElement jsonAddresses, FailoverType type, DnsQuestionRecord question, uint appRecordTtl, string healthCheck, Uri healthCheckUrl, List answers)
{
- if (jsonAddresses == null)
- return;
-
- foreach (dynamic jsonAddress in jsonAddresses)
+ foreach (JsonElement jsonAddress in jsonAddresses.EnumerateArray())
{
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
HealthCheckResponse response = _healthService.QueryStatus(address, healthCheck, healthCheckUrl, false);
string text = "app=failover; addressType=" + type.ToString() + "; address=" + address.ToString() + "; healthCheck=" + healthCheck + (healthCheckUrl is null ? "" : "; healthCheckUrl=" + healthCheckUrl.AbsoluteUri) + "; healthStatus=" + response.Status.ToString() + ";";
@@ -145,7 +139,7 @@ namespace Failover
if (_healthService is null)
_healthService = HealthService.Create(dnsServer);
- _healthService.Initialize(JsonConvert.DeserializeObject(config));
+ _healthService.Initialize(config);
return Task.CompletedTask;
}
@@ -158,17 +152,18 @@ namespace Failover
case DnsResourceRecordType.A:
case DnsResourceRecordType.AAAA:
{
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
- string healthCheck = jsonAppRecordData.healthCheck?.Value;
+ string healthCheck = jsonAppRecordData.GetPropertyValue("healthCheck", null);
Uri healthCheckUrl = null;
if (_healthService.HealthChecks.TryGetValue(healthCheck, out HealthCheck hc) && ((hc.Type == HealthCheckType.Https) || (hc.Type == HealthCheckType.Http)) && (hc.Url is null))
{
//read health check url only for http/https type checks and only when app config does not have an url configured
- if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
+ if (jsonAppRecordData.TryGetProperty("healthCheckUrl", out JsonElement jsonHealthCheckUrl) && (jsonHealthCheckUrl.ValueKind != JsonValueKind.Null))
{
- healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
+ healthCheckUrl = new Uri(jsonHealthCheckUrl.GetString());
}
else
{
@@ -181,19 +176,23 @@ namespace Failover
List answers = new List();
- GetAnswers(jsonAppRecordData.primary, question, appRecordTtl, healthCheck, healthCheckUrl, answers);
+ if (jsonAppRecordData.TryGetProperty("primary", out JsonElement jsonPrimary))
+ GetAnswers(jsonPrimary, question, appRecordTtl, healthCheck, healthCheckUrl, answers);
+
if (answers.Count == 0)
{
- GetAnswers(jsonAppRecordData.secondary, question, appRecordTtl, healthCheck, healthCheckUrl, answers);
+ if (jsonAppRecordData.TryGetProperty("secondary", out JsonElement jsonSecondary))
+ GetAnswers(jsonSecondary, question, appRecordTtl, healthCheck, healthCheckUrl, answers);
+
if (answers.Count == 0)
{
- if (jsonAppRecordData.serverDown is not null)
+ if (jsonAppRecordData.TryGetProperty("serverDown", out JsonElement jsonServerDown))
{
if (question.Type == DnsResourceRecordType.A)
{
- foreach (dynamic jsonAddress in jsonAppRecordData.serverDown)
+ foreach (JsonElement jsonAddress in jsonServerDown.EnumerateArray())
{
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
if (address.AddressFamily == AddressFamily.InterNetwork)
answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, question.Class, 30, new DnsARecordData(address)));
@@ -201,9 +200,9 @@ namespace Failover
}
else
{
- foreach (dynamic jsonAddress in jsonAppRecordData.serverDown)
+ foreach (JsonElement jsonAddress in jsonServerDown.EnumerateArray())
{
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
if (address.AddressFamily == AddressFamily.InterNetworkV6)
answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, question.Class, 30, new DnsAAAARecordData(address)));
@@ -219,32 +218,27 @@ namespace Failover
if (answers.Count > 1)
answers.Shuffle();
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers));
}
case DnsResourceRecordType.TXT:
{
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
-
- bool allowTxtStatus;
-
- if (jsonAppRecordData.allowTxtStatus == null)
- allowTxtStatus = false;
- else
- allowTxtStatus = jsonAppRecordData.allowTxtStatus.Value;
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ bool allowTxtStatus = jsonAppRecordData.GetPropertyValue("allowTxtStatus", false);
if (!allowTxtStatus)
return Task.FromResult(null);
- string healthCheck = jsonAppRecordData.healthCheck?.Value;
+ string healthCheck = jsonAppRecordData.GetPropertyValue("healthCheck", null);
Uri healthCheckUrl = null;
if (_healthService.HealthChecks.TryGetValue(healthCheck, out HealthCheck hc) && ((hc.Type == HealthCheckType.Https) || (hc.Type == HealthCheckType.Http)) && (hc.Url is null))
{
//read health check url only for http/https type checks and only when app config does not have an url configured
- if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
+ if (jsonAppRecordData.TryGetProperty("healthCheckUrl", out JsonElement jsonHealthCheckUrl) && (jsonHealthCheckUrl.ValueKind != JsonValueKind.Null))
{
- healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
+ healthCheckUrl = new Uri(jsonHealthCheckUrl.GetString());
}
else
{
@@ -257,10 +251,13 @@ namespace Failover
List answers = new List();
- GetStatusAnswers(jsonAppRecordData.primary, FailoverType.Primary, question, 30, healthCheck, healthCheckUrl, answers);
- GetStatusAnswers(jsonAppRecordData.secondary, FailoverType.Secondary, question, 30, healthCheck, healthCheckUrl, answers);
+ if (jsonAppRecordData.TryGetProperty("primary", out JsonElement jsonPrimary))
+ GetStatusAnswers(jsonPrimary, FailoverType.Primary, question, 30, healthCheck, healthCheckUrl, answers);
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers));
+ if (jsonAppRecordData.TryGetProperty("secondary", out JsonElement jsonSecondary))
+ GetStatusAnswers(jsonSecondary, FailoverType.Secondary, question, 30, healthCheck, healthCheckUrl, answers);
+
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers));
}
default:
diff --git a/Apps/FailoverApp/CNAME.cs b/Apps/FailoverApp/CNAME.cs
index e6dcd076..c00dff30 100644
--- a/Apps/FailoverApp/CNAME.cs
+++ b/Apps/FailoverApp/CNAME.cs
@@ -18,11 +18,12 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
@@ -119,17 +120,18 @@ namespace Failover
{
DnsQuestionRecord question = request.Question[0];
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
- string healthCheck = jsonAppRecordData.healthCheck?.Value;
+ string healthCheck = jsonAppRecordData.GetPropertyValue("healthCheck", null);
Uri healthCheckUrl = null;
if (_healthService.HealthChecks.TryGetValue(healthCheck, out HealthCheck hc) && ((hc.Type == HealthCheckType.Https) || (hc.Type == HealthCheckType.Http)) && (hc.Url is null))
{
//read health check url only for http/https type checks and only when app config does not have an url configured
- if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
+ if (jsonAppRecordData.TryGetProperty("healthCheckUrl", out JsonElement jsonHealthCheckUrl) && (jsonHealthCheckUrl.ValueKind != JsonValueKind.Null))
{
- healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
+ healthCheckUrl = new Uri(jsonHealthCheckUrl.GetString());
}
else
{
@@ -140,47 +142,50 @@ namespace Failover
}
}
- IReadOnlyList answers;
+ IReadOnlyList answers = null;
if (question.Type == DnsResourceRecordType.TXT)
{
- bool allowTxtStatus;
-
- if (jsonAppRecordData.allowTxtStatus == null)
- allowTxtStatus = false;
- else
- allowTxtStatus = jsonAppRecordData.allowTxtStatus.Value;
-
+ bool allowTxtStatus = jsonAppRecordData.GetPropertyValue("allowTxtStatus", false);
if (!allowTxtStatus)
return Task.FromResult(null);
List txtAnswers = new List();
- GetStatusAnswers(jsonAppRecordData.primary.Value, FailoverType.Primary, question, 30, healthCheck, healthCheckUrl, txtAnswers);
+ if (jsonAppRecordData.TryGetProperty("primary", out JsonElement jsonPrimary))
+ GetStatusAnswers(jsonPrimary.GetString(), FailoverType.Primary, question, 30, healthCheck, healthCheckUrl, txtAnswers);
- foreach (dynamic jsonDomain in jsonAppRecordData.secondary)
- GetStatusAnswers(jsonDomain.Value, FailoverType.Secondary, question, 30, healthCheck, healthCheckUrl, txtAnswers);
+ if (jsonAppRecordData.TryGetProperty("secondary", out JsonElement jsonSecondary))
+ {
+ foreach (JsonElement jsonDomain in jsonSecondary.EnumerateArray())
+ GetStatusAnswers(jsonDomain.GetString(), FailoverType.Secondary, question, 30, healthCheck, healthCheckUrl, txtAnswers);
+ }
answers = txtAnswers;
}
else
{
- answers = GetAnswers(jsonAppRecordData.primary.Value, question, zoneName, appRecordTtl, healthCheck, healthCheckUrl);
+ if (jsonAppRecordData.TryGetProperty("primary", out JsonElement jsonPrimary))
+ answers = GetAnswers(jsonPrimary.GetString(), question, zoneName, appRecordTtl, healthCheck, healthCheckUrl);
+
if (answers is null)
{
- foreach (dynamic jsonDomain in jsonAppRecordData.secondary)
+ if (jsonAppRecordData.TryGetProperty("secondary", out JsonElement jsonSecondary))
{
- answers = GetAnswers(jsonDomain.Value, question, zoneName, appRecordTtl, healthCheck, healthCheckUrl);
- if (answers is not null)
- break;
+ foreach (JsonElement jsonDomain in jsonSecondary.EnumerateArray())
+ {
+ answers = GetAnswers(jsonDomain.GetString(), question, zoneName, appRecordTtl, healthCheck, healthCheckUrl);
+ if (answers is not null)
+ break;
+ }
}
if (answers is null)
{
- if ((jsonAppRecordData.serverDown is null) || (jsonAppRecordData.serverDown.Value is null))
+ if (!jsonAppRecordData.TryGetProperty("serverDown", out JsonElement jsonServerDown) || (jsonServerDown.ValueKind == JsonValueKind.Null))
return Task.FromResult(null);
- string serverDown = jsonAppRecordData.serverDown.Value;
+ string serverDown = jsonServerDown.GetString();
if (question.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase)) //check for zone apex
answers = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.ANAME, DnsClass.IN, 30, new DnsANAMERecordData(serverDown)) }; //use ANAME
@@ -190,7 +195,7 @@ namespace Failover
}
}
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers));
}
#endregion
diff --git a/Apps/FailoverApp/EmailAlert.cs b/Apps/FailoverApp/EmailAlert.cs
index dea45264..d45c8d56 100644
--- a/Apps/FailoverApp/EmailAlert.cs
+++ b/Apps/FailoverApp/EmailAlert.cs
@@ -23,8 +23,10 @@ using System.Collections.Generic;
using System.Net;
using System.Net.Mail;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
using TechnitiumLibrary.Net.Mail;
@@ -37,7 +39,7 @@ namespace Failover
readonly HealthService _service;
- string _name;
+ readonly string _name;
bool _enabled;
MailAddress[] _alertTo;
string _smtpServer;
@@ -54,13 +56,15 @@ namespace Failover
#region constructor
- public EmailAlert(HealthService service, dynamic jsonEmailAlert)
+ public EmailAlert(HealthService service, JsonElement jsonEmailAlert)
{
_service = service;
_smtpClient = new SmtpClientEx();
_smtpClient.DnsClient = new DnsClientInternal(_service.DnsServer);
+ _name = jsonEmailAlert.GetPropertyValue("name", "default");
+
Reload(jsonEmailAlert);
}
@@ -98,7 +102,24 @@ namespace Failover
{
try
{
- await _smtpClient.SendMailAsync(message);
+ const int MAX_RETRIES = 3;
+ const int WAIT_INTERVAL = 30000;
+
+ for (int retries = 0; retries < MAX_RETRIES; retries++)
+ {
+ try
+ {
+ await _smtpClient.SendMailAsync(message);
+ break;
+ }
+ catch
+ {
+ if (retries == MAX_RETRIES - 1)
+ throw;
+
+ await Task.Delay(WAIT_INTERVAL);
+ }
+ }
}
catch (Exception ex)
{
@@ -110,71 +131,33 @@ namespace Failover
#region public
- public void Reload(dynamic jsonEmailAlert)
+ public void Reload(JsonElement jsonEmailAlert)
{
- if (jsonEmailAlert.name is null)
- _name = "default";
- else
- _name = jsonEmailAlert.name.Value;
+ _enabled = jsonEmailAlert.GetPropertyValue("enabled", false);
- if (jsonEmailAlert.enabled is null)
- _enabled = false;
+ if (jsonEmailAlert.TryReadArray("alertTo", delegate (string emailAddress) { return new MailAddress(emailAddress); }, out MailAddress[] alertTo))
+ _alertTo = alertTo;
else
- _enabled = jsonEmailAlert.enabled.Value;
-
- if (jsonEmailAlert.alertTo is null)
- {
_alertTo = null;
- }
- else
+
+ _smtpServer = jsonEmailAlert.GetPropertyValue("smtpServer", null);
+ _smtpPort = jsonEmailAlert.GetPropertyValue("smtpPort", 25);
+ _startTls = jsonEmailAlert.GetPropertyValue("startTls", false);
+ _smtpOverTls = jsonEmailAlert.GetPropertyValue("smtpOverTls", false);
+ _username = jsonEmailAlert.GetPropertyValue("username", null);
+ _password = jsonEmailAlert.GetPropertyValue("password", null);
+
+ if (jsonEmailAlert.TryGetProperty("mailFrom", out JsonElement jsonMailFrom))
{
- _alertTo = new MailAddress[jsonEmailAlert.alertTo.Count];
-
- for (int i = 0; i < _alertTo.Length; i++)
- _alertTo[i] = new MailAddress(jsonEmailAlert.alertTo[i].Value);
+ if (jsonEmailAlert.TryGetProperty("mailFromName", out JsonElement jsonMailFromName))
+ _mailFrom = new MailAddress(jsonMailFrom.GetString(), jsonMailFromName.GetString(), Encoding.UTF8);
+ else
+ _mailFrom = new MailAddress(jsonMailFrom.GetString());
}
-
- if (jsonEmailAlert.smtpServer is null)
- _smtpServer = null;
else
- _smtpServer = jsonEmailAlert.smtpServer.Value;
-
- if (jsonEmailAlert.smtpPort is null)
- _smtpPort = 25;
- else
- _smtpPort = Convert.ToInt32(jsonEmailAlert.smtpPort.Value);
-
- if (jsonEmailAlert.startTls is null)
- _startTls = false;
- else
- _startTls = jsonEmailAlert.startTls.Value;
-
- if (jsonEmailAlert.smtpOverTls is null)
- _smtpOverTls = false;
- else
- _smtpOverTls = jsonEmailAlert.smtpOverTls.Value;
-
- if (jsonEmailAlert.username is null)
- _username = null;
- else
- _username = jsonEmailAlert.username.Value;
-
- if (jsonEmailAlert.password is null)
- _password = null;
- else
- _password = jsonEmailAlert.password.Value;
-
- if (jsonEmailAlert.mailFrom is null)
{
_mailFrom = null;
}
- else
- {
- if (jsonEmailAlert.mailFromName is null)
- _mailFrom = new MailAddress(jsonEmailAlert.mailFrom.Value);
- else
- _mailFrom = new MailAddress(jsonEmailAlert.mailFrom.Value, jsonEmailAlert.mailFromName.Value, Encoding.UTF8);
- }
//update smtp client settings
_smtpClient.Host = _smtpServer;
diff --git a/Apps/FailoverApp/FailoverApp.csproj b/Apps/FailoverApp/FailoverApp.csproj
index 209c7eed..63a84a75 100644
--- a/Apps/FailoverApp/FailoverApp.csproj
+++ b/Apps/FailoverApp/FailoverApp.csproj
@@ -3,7 +3,7 @@
net7.0
false
- 5.1
+ 6.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -11,15 +11,11 @@
Failover
https://technitium.com/dns/
https://github.com/TechnitiumSoftware/DnsServer
- Allows creating APP records in a primary zone that can return A or AAAA records, or CNAME record based on the health status of the servers. The app supports email alerts and web hooks to relay the health status.
+ Allows creating APP records in a primary and forwarder zones that can return A or AAAA records, or CNAME record based on the health status of the servers. The app supports email alerts and web hooks to relay the health status.
false
Library
-
-
-
-
false
diff --git a/Apps/FailoverApp/HealthCheck.cs b/Apps/FailoverApp/HealthCheck.cs
index ce202abd..85c02a2d 100644
--- a/Apps/FailoverApp/HealthCheck.cs
+++ b/Apps/FailoverApp/HealthCheck.cs
@@ -23,6 +23,7 @@ using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary;
using TechnitiumLibrary.Net;
@@ -49,7 +50,7 @@ namespace Failover
readonly HealthService _service;
- string _name;
+ readonly string _name;
HealthCheckType _type;
int _interval;
int _retries;
@@ -66,10 +67,12 @@ namespace Failover
#region constructor
- public HealthCheck(HealthService service, dynamic jsonHealthCheck)
+ public HealthCheck(HealthService service, JsonElement jsonHealthCheck)
{
_service = service;
+ _name = jsonHealthCheck.GetPropertyValue("name", "default");
+
Reload(jsonHealthCheck);
}
@@ -200,63 +203,25 @@ namespace Failover
#region public
- public void Reload(dynamic jsonHealthCheck)
+ public void Reload(JsonElement jsonHealthCheck)
{
- if (jsonHealthCheck.name is null)
- _name = "default";
- else
- _name = jsonHealthCheck.name.Value;
+ _type = Enum.Parse(jsonHealthCheck.GetPropertyValue("type", "Tcp"), true);
+ _interval = jsonHealthCheck.GetPropertyValue("interval", 60) * 1000;
+ _retries = jsonHealthCheck.GetPropertyValue("retries", 3);
+ _timeout = jsonHealthCheck.GetPropertyValue("timeout", 10) * 1000;
+ _port = jsonHealthCheck.GetPropertyValue("port", 80);
- if (jsonHealthCheck.type == null)
- _type = HealthCheckType.Tcp;
+ if (jsonHealthCheck.TryGetProperty("url", out JsonElement jsonUrl) && (jsonUrl.ValueKind != JsonValueKind.Null))
+ _url = new Uri(jsonUrl.GetString());
else
- _type = Enum.Parse(jsonHealthCheck.type.Value, true);
-
- if (jsonHealthCheck.interval is null)
- _interval = 60000;
- else
- _interval = Convert.ToInt32(jsonHealthCheck.interval.Value) * 1000;
-
- if (jsonHealthCheck.retries is null)
- _retries = 3;
- else
- _retries = Convert.ToInt32(jsonHealthCheck.retries.Value);
-
- if (jsonHealthCheck.timeout is null)
- _timeout = 10000;
- else
- _timeout = Convert.ToInt32(jsonHealthCheck.timeout.Value) * 1000;
-
- if (jsonHealthCheck.port is null)
- _port = 80;
- else
- _port = Convert.ToInt32(jsonHealthCheck.port.Value);
-
- if ((jsonHealthCheck.url is null) || (jsonHealthCheck.url.Value is null))
_url = null;
- else
- _url = new Uri(jsonHealthCheck.url.Value);
- string emailAlertName;
-
- if (jsonHealthCheck.emailAlert is null)
- emailAlertName = null;
- else
- emailAlertName = jsonHealthCheck.emailAlert.Value;
-
- if ((emailAlertName is not null) && _service.EmailAlerts.TryGetValue(emailAlertName, out EmailAlert emailAlert))
+ if (jsonHealthCheck.TryGetProperty("emailAlert", out JsonElement jsonEmailAlert) && _service.EmailAlerts.TryGetValue(jsonEmailAlert.GetString(), out EmailAlert emailAlert))
_emailAlert = emailAlert;
else
_emailAlert = null;
- string webHookName;
-
- if (jsonHealthCheck.webHook is null)
- webHookName = null;
- else
- webHookName = jsonHealthCheck.webHook.Value;
-
- if ((webHookName is not null) && _service.WebHooks.TryGetValue(webHookName, out WebHook webHook))
+ if (jsonHealthCheck.TryGetProperty("webHook", out JsonElement jsonWebHook) && _service.WebHooks.TryGetValue(jsonWebHook.GetString(), out WebHook webHook))
_webHook = webHook;
else
_webHook = null;
diff --git a/Apps/FailoverApp/HealthService.cs b/Apps/FailoverApp/HealthService.cs
index adf55513..99536d0d 100644
--- a/Apps/FailoverApp/HealthService.cs
+++ b/Apps/FailoverApp/HealthService.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,7 +22,9 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
+using System.Text.Json;
using System.Threading;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
@@ -178,19 +180,19 @@ namespace Failover
#region public
- public void Initialize(dynamic jsonConfig)
+ public void Initialize(string config)
{
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
+
//email alerts
{
- //add or update email alerts
- foreach (dynamic jsonEmailAlert in jsonConfig.emailAlerts)
- {
- string name;
+ JsonElement jsonEmailAlerts = jsonConfig.GetProperty("emailAlerts");
- if (jsonEmailAlert.name is null)
- name = "default";
- else
- name = jsonEmailAlert.name.Value;
+ //add or update email alerts
+ foreach (JsonElement jsonEmailAlert in jsonEmailAlerts.EnumerateArray())
+ {
+ string name = jsonEmailAlert.GetPropertyValue("name", "default");
if (_emailAlerts.TryGetValue(name, out EmailAlert existingEmailAlert))
{
@@ -201,7 +203,6 @@ namespace Failover
{
//add
EmailAlert emailAlert = new EmailAlert(this, jsonEmailAlert);
-
_emailAlerts.TryAdd(emailAlert.Name, emailAlert);
}
}
@@ -211,15 +212,9 @@ namespace Failover
{
bool emailAlertExists = false;
- foreach (dynamic jsonEmailAlert in jsonConfig.emailAlerts)
+ foreach (JsonElement jsonEmailAlert in jsonEmailAlerts.EnumerateArray())
{
- string name;
-
- if (jsonEmailAlert.name is null)
- name = "default";
- else
- name = jsonEmailAlert.name.Value;
-
+ string name = jsonEmailAlert.GetPropertyValue("name", "default");
if (name == emailAlert.Key)
{
emailAlertExists = true;
@@ -237,15 +232,12 @@ namespace Failover
//web hooks
{
- //add or update email alerts
- foreach (dynamic jsonWebHook in jsonConfig.webHooks)
- {
- string name;
+ JsonElement jsonWebHooks = jsonConfig.GetProperty("webHooks");
- if (jsonWebHook.name is null)
- name = "default";
- else
- name = jsonWebHook.name.Value;
+ //add or update email alerts
+ foreach (JsonElement jsonWebHook in jsonWebHooks.EnumerateArray())
+ {
+ string name = jsonWebHook.GetPropertyValue("name", "default");
if (_webHooks.TryGetValue(name, out WebHook existingWebHook))
{
@@ -256,7 +248,6 @@ namespace Failover
{
//add
WebHook webHook = new WebHook(this, jsonWebHook);
-
_webHooks.TryAdd(webHook.Name, webHook);
}
}
@@ -266,15 +257,9 @@ namespace Failover
{
bool webHookExists = false;
- foreach (dynamic jsonWebHook in jsonConfig.webHooks)
+ foreach (JsonElement jsonWebHook in jsonWebHooks.EnumerateArray())
{
- string name;
-
- if (jsonWebHook.name is null)
- name = "default";
- else
- name = jsonWebHook.name.Value;
-
+ string name = jsonWebHook.GetPropertyValue("name", "default");
if (name == webHook.Key)
{
webHookExists = true;
@@ -292,15 +277,12 @@ namespace Failover
//health checks
{
- //add or update health checks
- foreach (dynamic jsonHealthCheck in jsonConfig.healthChecks)
- {
- string name;
+ JsonElement jsonHealthChecks = jsonConfig.GetProperty("healthChecks");
- if (jsonHealthCheck.name is null)
- name = "default";
- else
- name = jsonHealthCheck.name.Value;
+ //add or update health checks
+ foreach (JsonElement jsonHealthCheck in jsonHealthChecks.EnumerateArray())
+ {
+ string name = jsonHealthCheck.GetPropertyValue("name", "default");
if (_healthChecks.TryGetValue(name, out HealthCheck existingHealthCheck))
{
@@ -311,7 +293,6 @@ namespace Failover
{
//add
HealthCheck healthCheck = new HealthCheck(this, jsonHealthCheck);
-
_healthChecks.TryAdd(healthCheck.Name, healthCheck);
}
}
@@ -321,15 +302,9 @@ namespace Failover
{
bool healthCheckExists = false;
- foreach (dynamic jsonHealthCheck in jsonConfig.healthChecks)
+ foreach (JsonElement jsonHealthCheck in jsonHealthChecks.EnumerateArray())
{
- string name;
-
- if (jsonHealthCheck.name is null)
- name = "default";
- else
- name = jsonHealthCheck.name.Value;
-
+ string name = jsonHealthCheck.GetPropertyValue("name", "default");
if (name == healthCheck.Key)
{
healthCheckExists = true;
@@ -353,14 +328,21 @@ namespace Failover
//under maintenance networks
_underMaintenance.Clear();
- if (jsonConfig.underMaintenance is not null)
+ if (jsonConfig.TryGetProperty("underMaintenance", out JsonElement jsonUnderMaintenance))
{
- foreach (dynamic jsonNetwork in jsonConfig.underMaintenance)
+ foreach (JsonElement jsonNetwork in jsonUnderMaintenance.EnumerateArray())
{
- string network = jsonNetwork.network.Value;
- bool enable = jsonNetwork.enable.Value;
+ string network = jsonNetwork.GetProperty("network").GetString();
+ bool enabled;
- _underMaintenance.TryAdd(NetworkAddress.Parse(network), enable);
+ if (jsonNetwork.TryGetProperty("enabled", out JsonElement jsonEnabled))
+ enabled = jsonEnabled.GetBoolean();
+ else if (jsonNetwork.TryGetProperty("enable", out JsonElement jsonEnable))
+ enabled = jsonEnable.GetBoolean();
+ else
+ enabled = true;
+
+ _underMaintenance.TryAdd(NetworkAddress.Parse(network), enabled);
}
}
}
diff --git a/Apps/FailoverApp/WebHook.cs b/Apps/FailoverApp/WebHook.cs
index 7aecc7f0..5356c972 100644
--- a/Apps/FailoverApp/WebHook.cs
+++ b/Apps/FailoverApp/WebHook.cs
@@ -17,14 +17,15 @@ along with this program. If not, see .
*/
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Text.Json;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
using TechnitiumLibrary.Net.Proxy;
@@ -36,7 +37,7 @@ namespace Failover
readonly HealthService _service;
- string _name;
+ readonly string _name;
bool _enabled;
Uri[] _urls;
@@ -47,10 +48,12 @@ namespace Failover
#region constructor
- public WebHook(HealthService service, dynamic jsonWebHook)
+ public WebHook(HealthService service, JsonElement jsonWebHook)
{
_service = service;
+ _name = jsonWebHook.GetPropertyValue("name", "default");
+
Reload(jsonWebHook);
}
@@ -176,29 +179,14 @@ namespace Failover
#region public
- public void Reload(dynamic jsonWebHook)
+ public void Reload(JsonElement jsonWebHook)
{
- if (jsonWebHook.name is null)
- _name = "default";
- else
- _name = jsonWebHook.name.Value;
+ _enabled = jsonWebHook.GetPropertyValue("enabled", false);
- if (jsonWebHook.enabled is null)
- _enabled = false;
+ if (jsonWebHook.TryReadArray("urls", delegate (string uri) { return new Uri(uri); }, out Uri[] urls))
+ _urls = urls;
else
- _enabled = jsonWebHook.enabled.Value;
-
- if (jsonWebHook.urls is null)
- {
_urls = null;
- }
- else
- {
- _urls = new Uri[jsonWebHook.urls.Count];
-
- for (int i = 0; i < _urls.Length; i++)
- _urls[i] = new Uri(jsonWebHook.urls[i].Value);
- }
ConditionalHttpReload();
}
@@ -212,26 +200,17 @@ namespace Failover
{
using (MemoryStream mS = new MemoryStream())
{
- JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS));
+ Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS);
jsonWriter.WriteStartObject();
- jsonWriter.WritePropertyName("address");
- jsonWriter.WriteValue(address.ToString());
-
- jsonWriter.WritePropertyName("healthCheck");
- jsonWriter.WriteValue(healthCheck);
-
- jsonWriter.WritePropertyName("status");
- jsonWriter.WriteValue(healthCheckResponse.Status.ToString());
+ jsonWriter.WriteString("address", address.ToString());
+ jsonWriter.WriteString("healthCheck", healthCheck);
+ jsonWriter.WriteString("status", healthCheckResponse.Status.ToString());
if (healthCheckResponse.Status == HealthStatus.Failed)
- {
- jsonWriter.WritePropertyName("failureReason");
- jsonWriter.WriteValue(healthCheckResponse.FailureReason);
- }
+ jsonWriter.WriteString("failureReason", healthCheckResponse.FailureReason);
- jsonWriter.WritePropertyName("dateTime");
- jsonWriter.WriteValue(healthCheckResponse.DateTime);
+ jsonWriter.WriteString("dateTime", healthCheckResponse.DateTime);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
@@ -253,23 +232,14 @@ namespace Failover
{
using (MemoryStream mS = new MemoryStream())
{
- JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS));
+ Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS);
jsonWriter.WriteStartObject();
- jsonWriter.WritePropertyName("address");
- jsonWriter.WriteValue(address.ToString());
-
- jsonWriter.WritePropertyName("healthCheck");
- jsonWriter.WriteValue(healthCheck);
-
- jsonWriter.WritePropertyName("status");
- jsonWriter.WriteValue("Error");
-
- jsonWriter.WritePropertyName("failureReason");
- jsonWriter.WriteValue(ex.ToString());
-
- jsonWriter.WritePropertyName("dateTime");
- jsonWriter.WriteValue(DateTime.UtcNow);
+ jsonWriter.WriteString("address", address.ToString());
+ jsonWriter.WriteString("healthCheck", healthCheck);
+ jsonWriter.WriteString("status", "Error");
+ jsonWriter.WriteString("failureReason", ex.ToString());
+ jsonWriter.WriteString("dateTime", DateTime.UtcNow);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
@@ -291,29 +261,18 @@ namespace Failover
{
using (MemoryStream mS = new MemoryStream())
{
- JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS));
+ Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS);
jsonWriter.WriteStartObject();
- jsonWriter.WritePropertyName("domain");
- jsonWriter.WriteValue(domain);
-
- jsonWriter.WritePropertyName("recordType");
- jsonWriter.WriteValue(type.ToString());
-
- jsonWriter.WritePropertyName("healthCheck");
- jsonWriter.WriteValue(healthCheck);
-
- jsonWriter.WritePropertyName("status");
- jsonWriter.WriteValue(healthCheckResponse.Status.ToString());
+ jsonWriter.WriteString("domain", domain);
+ jsonWriter.WriteString("recordType", type.ToString());
+ jsonWriter.WriteString("healthCheck", healthCheck);
+ jsonWriter.WriteString("status", healthCheckResponse.Status.ToString());
if (healthCheckResponse.Status == HealthStatus.Failed)
- {
- jsonWriter.WritePropertyName("failureReason");
- jsonWriter.WriteValue(healthCheckResponse.FailureReason);
- }
+ jsonWriter.WriteString("failureReason", healthCheckResponse.FailureReason);
- jsonWriter.WritePropertyName("dateTime");
- jsonWriter.WriteValue(healthCheckResponse.DateTime);
+ jsonWriter.WriteString("dateTime", healthCheckResponse.DateTime);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
@@ -335,26 +294,15 @@ namespace Failover
{
using (MemoryStream mS = new MemoryStream())
{
- JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS));
+ Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS);
jsonWriter.WriteStartObject();
- jsonWriter.WritePropertyName("domain");
- jsonWriter.WriteValue(domain);
-
- jsonWriter.WritePropertyName("recordType");
- jsonWriter.WriteValue(type.ToString());
-
- jsonWriter.WritePropertyName("healthCheck");
- jsonWriter.WriteValue(healthCheck);
-
- jsonWriter.WritePropertyName("status");
- jsonWriter.WriteValue("Error");
-
- jsonWriter.WritePropertyName("failureReason");
- jsonWriter.WriteValue(ex.ToString());
-
- jsonWriter.WritePropertyName("dateTime");
- jsonWriter.WriteValue(DateTime.UtcNow);
+ jsonWriter.WriteString("domain", domain);
+ jsonWriter.WriteString("recordType", type.ToString());
+ jsonWriter.WriteString("healthCheck", healthCheck);
+ jsonWriter.WriteString("status", "Error");
+ jsonWriter.WriteString("failureReason", ex.ToString());
+ jsonWriter.WriteString("dateTime", DateTime.UtcNow);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
diff --git a/Apps/FailoverApp/dnsApp.config b/Apps/FailoverApp/dnsApp.config
index f4954e9b..ced9d001 100644
--- a/Apps/FailoverApp/dnsApp.config
+++ b/Apps/FailoverApp/dnsApp.config
@@ -89,11 +89,11 @@
"underMaintenance": [
{
"network": "192.168.10.2/32",
- "enable": false
+ "enabled": false
},
{
"network": "10.1.1.0/24",
- "enable": false
+ "enabled": false
}
]
}
\ No newline at end of file
diff --git a/Apps/GeoContinentApp/Address.cs b/Apps/GeoContinentApp/Address.cs
index 65cd833c..38ed82d2 100644
--- a/Apps/GeoContinentApp/Address.cs
+++ b/Apps/GeoContinentApp/Address.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,10 +19,10 @@ along with this program. If not, see .
using DnsServerCore.ApplicationCommon;
using MaxMind.GeoIP2.Responses;
-using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns;
@@ -82,86 +82,87 @@ namespace GeoContinent
{
case DnsResourceRecordType.A:
case DnsResourceRecordType.AAAA:
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
- dynamic jsonContinent = null;
-
- bool ecsUsed = false;
- EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
- if (requestECS is not null)
+ using (JsonDocument jsonDocument = JsonDocument.Parse(appRecordData))
{
- if (_maxMind.DatabaseReader.TryCountry(requestECS.Address, out CountryResponse csResponse))
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ JsonElement jsonContinent = default;
+
+ bool ecsUsed = false;
+ EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
+ if (requestECS is not null)
{
- ecsUsed = true;
- jsonContinent = jsonAppRecordData[csResponse.Continent.Code];
- if (jsonContinent is null)
- jsonContinent = jsonAppRecordData["default"];
+ if (_maxMind.DatabaseReader.TryCountry(requestECS.Address, out CountryResponse csResponse))
+ {
+ ecsUsed = true;
+ if (!jsonAppRecordData.TryGetProperty(csResponse.Continent.Code, out jsonContinent))
+ jsonAppRecordData.TryGetProperty("default", out jsonContinent);
+ }
}
- }
- if (jsonContinent is null)
- {
- if (_maxMind.DatabaseReader.TryCountry(remoteEP.Address, out CountryResponse response))
+ if (jsonContinent.ValueKind == JsonValueKind.Undefined)
{
- jsonContinent = jsonAppRecordData[response.Continent.Code];
- if (jsonContinent is null)
- jsonContinent = jsonAppRecordData["default"];
+ if (_maxMind.DatabaseReader.TryCountry(remoteEP.Address, out CountryResponse response))
+ {
+ if (!jsonAppRecordData.TryGetProperty(response.Continent.Code, out jsonContinent))
+ jsonAppRecordData.TryGetProperty("default", out jsonContinent);
+ }
+ else
+ {
+ jsonAppRecordData.TryGetProperty("default", out jsonContinent);
+ }
+
+ if (jsonContinent.ValueKind == JsonValueKind.Undefined)
+ return Task.FromResult(null);
+ }
+
+ List answers = new List();
+
+ switch (question.Type)
+ {
+ case DnsResourceRecordType.A:
+ foreach (JsonElement jsonAddress in jsonContinent.EnumerateArray())
+ {
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
+
+ if (address.AddressFamily == AddressFamily.InterNetwork)
+ answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
+ }
+ break;
+
+ case DnsResourceRecordType.AAAA:
+ foreach (JsonElement jsonAddress in jsonContinent.EnumerateArray())
+ {
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
+
+ if (address.AddressFamily == AddressFamily.InterNetworkV6)
+ answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
+ }
+ break;
+ }
+
+ if (answers.Count == 0)
+ return Task.FromResult(null);
+
+ if (answers.Count > 1)
+ answers.Shuffle();
+
+ EDnsOption[] options;
+
+ if (requestECS is null)
+ {
+ options = null;
}
else
{
- jsonContinent = jsonAppRecordData["default"];
+ if (ecsUsed)
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.Address);
+ else
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.Address);
}
+
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
}
- if (jsonContinent is null)
- return Task.FromResult(null);
-
- List answers = new List();
-
- switch (question.Type)
- {
- case DnsResourceRecordType.A:
- foreach (dynamic jsonAddress in jsonContinent)
- {
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
-
- if (address.AddressFamily == AddressFamily.InterNetwork)
- answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
- }
- break;
-
- case DnsResourceRecordType.AAAA:
- foreach (dynamic jsonAddress in jsonContinent)
- {
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
-
- if (address.AddressFamily == AddressFamily.InterNetworkV6)
- answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
- }
- break;
- }
-
- if (answers.Count == 0)
- return Task.FromResult(null);
-
- if (answers.Count > 1)
- answers.Shuffle();
-
- EDnsOption[] options;
-
- if (requestECS is null)
- {
- options = null;
- }
- else
- {
- if (ecsUsed)
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.AddressValue);
- else
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.AddressValue);
- }
-
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
-
default:
return Task.FromResult(null);
}
diff --git a/Apps/GeoContinentApp/CNAME.cs b/Apps/GeoContinentApp/CNAME.cs
index 1893360b..b0dbfb6a 100644
--- a/Apps/GeoContinentApp/CNAME.cs
+++ b/Apps/GeoContinentApp/CNAME.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,10 +19,10 @@ along with this program. If not, see .
using DnsServerCore.ApplicationCommon;
using MaxMind.GeoIP2.Responses;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.EDnsOptions;
@@ -76,8 +76,9 @@ namespace GeoContinent
public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
{
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
- dynamic jsonContinent = null;
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ JsonElement jsonContinent = default;
bool ecsUsed = false;
EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
@@ -86,30 +87,28 @@ namespace GeoContinent
if (_maxMind.DatabaseReader.TryCountry(requestECS.Address, out CountryResponse csResponse))
{
ecsUsed = true;
- jsonContinent = jsonAppRecordData[csResponse.Continent.Code];
- if (jsonContinent is null)
- jsonContinent = jsonAppRecordData["default"];
+ if (!jsonAppRecordData.TryGetProperty(csResponse.Continent.Code, out jsonContinent))
+ jsonAppRecordData.TryGetProperty("default", out jsonContinent);
}
}
- if (jsonContinent is null)
+ if (jsonContinent.ValueKind == JsonValueKind.Undefined)
{
if (_maxMind.DatabaseReader.TryCountry(remoteEP.Address, out CountryResponse response))
{
- jsonContinent = jsonAppRecordData[response.Continent.Code];
- if (jsonContinent is null)
- jsonContinent = jsonAppRecordData["default"];
+ if (!jsonAppRecordData.TryGetProperty(response.Continent.Code, out jsonContinent))
+ jsonAppRecordData.TryGetProperty("default", out jsonContinent);
}
else
{
- jsonContinent = jsonAppRecordData["default"];
+ jsonAppRecordData.TryGetProperty("default", out jsonContinent);
}
+
+ if (jsonContinent.ValueKind == JsonValueKind.Undefined)
+ return Task.FromResult(null);
}
- if (jsonContinent is null)
- return Task.FromResult(null);
-
- string cname = jsonContinent.Value;
+ string cname = jsonContinent.GetString();
if (string.IsNullOrEmpty(cname))
return Task.FromResult(null);
@@ -129,12 +128,12 @@ namespace GeoContinent
else
{
if (ecsUsed)
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.AddressValue);
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.Address);
else
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.AddressValue);
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.Address);
}
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
}
#endregion
diff --git a/Apps/GeoContinentApp/GeoContinentApp.csproj b/Apps/GeoContinentApp/GeoContinentApp.csproj
index fae5dc65..6b6cf7cf 100644
--- a/Apps/GeoContinentApp/GeoContinentApp.csproj
+++ b/Apps/GeoContinentApp/GeoContinentApp.csproj
@@ -4,7 +4,7 @@
net7.0
false
true
- 5.0.1
+ 6.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -12,14 +12,13 @@
GeoContinent
https://technitium.com/dns/
https://github.com/TechnitiumSoftware/DnsServer
- Allows creating APP records in a primary zone that can return A or AAAA records, or CNAME record based on the continent the client queries from using MaxMind GeoIP2 Country database. Supports EDNS Client Subnet (ECS). This app requires MaxMind GeoIP2 database and includes the GeoLite2 version for trial. \n\nTo update the MaxMind GeoIP2 database for your app, download the GeoIP2-Country.mmdb file from MaxMind and zip it. Use the zip file with the manual Update option.
+ Allows creating APP records in a primary and forwarder zones that can return A or AAAA records, or CNAME record based on the continent the client queries from using MaxMind GeoIP2 Country database. Supports EDNS Client Subnet (ECS). This app requires MaxMind GeoIP2 database and includes the GeoLite2 version for trial. \n\nTo update the MaxMind GeoIP2 database for your app, download the GeoIP2-Country.mmdb file from MaxMind and zip it. Use the zip file with the manual Update option.
false
Library
-
diff --git a/Apps/GeoCountryApp/Address.cs b/Apps/GeoCountryApp/Address.cs
index ade2a668..c1990b95 100644
--- a/Apps/GeoCountryApp/Address.cs
+++ b/Apps/GeoCountryApp/Address.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,10 +19,10 @@ along with this program. If not, see .
using DnsServerCore.ApplicationCommon;
using MaxMind.GeoIP2.Responses;
-using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns;
@@ -82,86 +82,87 @@ namespace GeoCountry
{
case DnsResourceRecordType.A:
case DnsResourceRecordType.AAAA:
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
- dynamic jsonCountry = null;
-
- bool ecsUsed = false;
- EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
- if (requestECS is not null)
+ using (JsonDocument jsonDocument = JsonDocument.Parse(appRecordData))
{
- if (_maxMind.DatabaseReader.TryCountry(requestECS.Address, out CountryResponse csResponse))
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ JsonElement jsonCountry = default;
+
+ bool ecsUsed = false;
+ EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
+ if (requestECS is not null)
{
- ecsUsed = true;
- jsonCountry = jsonAppRecordData[csResponse.Country.IsoCode];
- if (jsonCountry is null)
- jsonCountry = jsonAppRecordData["default"];
+ if (_maxMind.DatabaseReader.TryCountry(requestECS.Address, out CountryResponse csResponse))
+ {
+ ecsUsed = true;
+ if (!jsonAppRecordData.TryGetProperty(csResponse.Country.IsoCode, out jsonCountry))
+ jsonAppRecordData.TryGetProperty("default", out jsonCountry);
+ }
}
- }
- if (jsonCountry is null)
- {
- if (_maxMind.DatabaseReader.TryCountry(remoteEP.Address, out CountryResponse response))
+ if (jsonCountry.ValueKind == JsonValueKind.Undefined)
{
- jsonCountry = jsonAppRecordData[response.Country.IsoCode];
- if (jsonCountry is null)
- jsonCountry = jsonAppRecordData["default"];
+ if (_maxMind.DatabaseReader.TryCountry(remoteEP.Address, out CountryResponse response))
+ {
+ if (!jsonAppRecordData.TryGetProperty(response.Country.IsoCode, out jsonCountry))
+ jsonAppRecordData.TryGetProperty("default", out jsonCountry);
+ }
+ else
+ {
+ jsonAppRecordData.TryGetProperty("default", out jsonCountry);
+ }
+
+ if (jsonCountry.ValueKind == JsonValueKind.Undefined)
+ return Task.FromResult(null);
+ }
+
+ List answers = new List();
+
+ switch (question.Type)
+ {
+ case DnsResourceRecordType.A:
+ foreach (JsonElement jsonAddress in jsonCountry.EnumerateArray())
+ {
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
+
+ if (address.AddressFamily == AddressFamily.InterNetwork)
+ answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
+ }
+ break;
+
+ case DnsResourceRecordType.AAAA:
+ foreach (JsonElement jsonAddress in jsonCountry.EnumerateArray())
+ {
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
+
+ if (address.AddressFamily == AddressFamily.InterNetworkV6)
+ answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
+ }
+ break;
+ }
+
+ if (answers.Count == 0)
+ return Task.FromResult(null);
+
+ if (answers.Count > 1)
+ answers.Shuffle();
+
+ EDnsOption[] options;
+
+ if (requestECS is null)
+ {
+ options = null;
}
else
{
- jsonCountry = jsonAppRecordData["default"];
+ if (ecsUsed)
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.Address);
+ else
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.Address);
}
+
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
}
- if (jsonCountry is null)
- return Task.FromResult(null);
-
- List answers = new List();
-
- switch (question.Type)
- {
- case DnsResourceRecordType.A:
- foreach (dynamic jsonAddress in jsonCountry)
- {
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
-
- if (address.AddressFamily == AddressFamily.InterNetwork)
- answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
- }
- break;
-
- case DnsResourceRecordType.AAAA:
- foreach (dynamic jsonAddress in jsonCountry)
- {
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
-
- if (address.AddressFamily == AddressFamily.InterNetworkV6)
- answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
- }
- break;
- }
-
- if (answers.Count == 0)
- return Task.FromResult(null);
-
- if (answers.Count > 1)
- answers.Shuffle();
-
- EDnsOption[] options;
-
- if (requestECS is null)
- {
- options = null;
- }
- else
- {
- if (ecsUsed)
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.AddressValue);
- else
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.AddressValue);
- }
-
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
-
default:
return Task.FromResult(null);
}
diff --git a/Apps/GeoCountryApp/CNAME.cs b/Apps/GeoCountryApp/CNAME.cs
index 237b06e3..4c2cc371 100644
--- a/Apps/GeoCountryApp/CNAME.cs
+++ b/Apps/GeoCountryApp/CNAME.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,10 +19,10 @@ along with this program. If not, see .
using DnsServerCore.ApplicationCommon;
using MaxMind.GeoIP2.Responses;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.EDnsOptions;
@@ -76,8 +76,9 @@ namespace GeoCountry
public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
{
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
- dynamic jsonCountry = null;
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ JsonElement jsonCountry = default;
bool ecsUsed = false;
EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
@@ -86,30 +87,28 @@ namespace GeoCountry
if (_maxMind.DatabaseReader.TryCountry(requestECS.Address, out CountryResponse csResponse))
{
ecsUsed = true;
- jsonCountry = jsonAppRecordData[csResponse.Country.IsoCode];
- if (jsonCountry is null)
- jsonCountry = jsonAppRecordData["default"];
+ if (!jsonAppRecordData.TryGetProperty(csResponse.Country.IsoCode, out jsonCountry))
+ jsonAppRecordData.TryGetProperty("default", out jsonCountry);
}
}
- if (jsonCountry is null)
+ if (jsonCountry.ValueKind == JsonValueKind.Undefined)
{
if (_maxMind.DatabaseReader.TryCountry(remoteEP.Address, out CountryResponse response))
{
- jsonCountry = jsonAppRecordData[response.Country.IsoCode];
- if (jsonCountry is null)
- jsonCountry = jsonAppRecordData["default"];
+ if (!jsonAppRecordData.TryGetProperty(response.Country.IsoCode, out jsonCountry))
+ jsonAppRecordData.TryGetProperty("default", out jsonCountry);
}
else
{
- jsonCountry = jsonAppRecordData["default"];
+ jsonAppRecordData.TryGetProperty("default", out jsonCountry);
}
+
+ if (jsonCountry.ValueKind == JsonValueKind.Undefined)
+ return Task.FromResult(null);
}
- if (jsonCountry is null)
- return Task.FromResult(null);
-
- string cname = jsonCountry.Value;
+ string cname = jsonCountry.GetString();
if (string.IsNullOrEmpty(cname))
return Task.FromResult(null);
@@ -129,12 +128,12 @@ namespace GeoCountry
else
{
if (ecsUsed)
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.AddressValue);
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.Address);
else
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.AddressValue);
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.Address);
}
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
}
#endregion
diff --git a/Apps/GeoCountryApp/GeoCountryApp.csproj b/Apps/GeoCountryApp/GeoCountryApp.csproj
index a54fd377..1fec56aa 100644
--- a/Apps/GeoCountryApp/GeoCountryApp.csproj
+++ b/Apps/GeoCountryApp/GeoCountryApp.csproj
@@ -4,7 +4,7 @@
net7.0
false
true
- 5.0.1
+ 6.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -12,14 +12,13 @@
GeoCountry
https://technitium.com/dns/
https://github.com/TechnitiumSoftware/DnsServer
- Allows creating APP records in a primary zone that can return A or AAAA records, or CNAME record based on the country the client queries from using MaxMind GeoIP2 Country database. Supports EDNS Client Subnet (ECS). This app requires MaxMind GeoIP2 database and includes the GeoLite2 version for trial. \n\nTo update the MaxMind GeoIP2 database for your app, download the GeoIP2-Country.mmdb file from MaxMind and zip it. Use the zip file with the manual Update option.
+ Allows creating APP records in a primary and forwarder zones that can return A or AAAA records, or CNAME record based on the country the client queries from using MaxMind GeoIP2 Country database. Supports EDNS Client Subnet (ECS). This app requires MaxMind GeoIP2 database and includes the GeoLite2 version for trial. \n\nTo update the MaxMind GeoIP2 database for your app, download the GeoIP2-Country.mmdb file from MaxMind and zip it. Use the zip file with the manual Update option.
false
Library
-
diff --git a/Apps/GeoDistanceApp/Address.cs b/Apps/GeoDistanceApp/Address.cs
index 3e4c5dd3..be6ebaca 100644
--- a/Apps/GeoDistanceApp/Address.cs
+++ b/Apps/GeoDistanceApp/Address.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,11 +20,11 @@ along with this program. If not, see .
using DnsServerCore.ApplicationCommon;
using MaxMind.GeoIP2.Model;
using MaxMind.GeoIP2.Responses;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns;
@@ -115,81 +115,85 @@ namespace GeoDistance
if ((location is null) && _maxMind.DatabaseReader.TryCity(remoteEP.Address, out CityResponse response) && response.Location.HasCoordinates)
location = response.Location;
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
- dynamic jsonClosestServer = null;
-
- if (location is null)
+ using (JsonDocument jsonDocument = JsonDocument.Parse(appRecordData))
{
- jsonClosestServer = jsonAppRecordData[0];
- }
- else
- {
- double lastDistance = double.MaxValue;
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ JsonElement jsonClosestServer = default;
- foreach (dynamic jsonServer in jsonAppRecordData)
+ if (location is null)
{
- double lat = Convert.ToDouble(jsonServer.lat.Value);
- double @long = Convert.ToDouble(jsonServer.@long.Value);
+ if (jsonAppRecordData.GetArrayLength() > 0)
+ jsonClosestServer = jsonAppRecordData[0];
+ }
+ else
+ {
+ double lastDistance = double.MaxValue;
- double distance = GetDistance(lat, @long, location.Latitude.Value, location.Longitude.Value);
-
- if (distance < lastDistance)
+ foreach (JsonElement jsonServer in jsonAppRecordData.EnumerateArray())
{
- lastDistance = distance;
- jsonClosestServer = jsonServer;
+ double lat = Convert.ToDouble(jsonServer.GetProperty("lat").GetString());
+ double @long = Convert.ToDouble(jsonServer.GetProperty("long").GetString());
+
+ double distance = GetDistance(lat, @long, location.Latitude.Value, location.Longitude.Value);
+
+ if (distance < lastDistance)
+ {
+ lastDistance = distance;
+ jsonClosestServer = jsonServer;
+ }
}
}
- }
- if (jsonClosestServer is null)
- return Task.FromResult(null);
+ if (jsonClosestServer.ValueKind == JsonValueKind.Undefined)
+ return Task.FromResult(null);
- List answers = new List();
+ List answers = new List();
- switch (question.Type)
- {
- case DnsResourceRecordType.A:
- foreach (dynamic jsonAddress in jsonClosestServer.addresses)
- {
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
+ switch (question.Type)
+ {
+ case DnsResourceRecordType.A:
+ foreach (JsonElement jsonAddress in jsonClosestServer.GetProperty("addresses").EnumerateArray())
+ {
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
- if (address.AddressFamily == AddressFamily.InterNetwork)
- answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
- }
- break;
+ if (address.AddressFamily == AddressFamily.InterNetwork)
+ answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
+ }
+ break;
- case DnsResourceRecordType.AAAA:
- foreach (dynamic jsonAddress in jsonClosestServer.addresses)
- {
- IPAddress address = IPAddress.Parse(jsonAddress.Value);
+ case DnsResourceRecordType.AAAA:
+ foreach (JsonElement jsonAddress in jsonClosestServer.GetProperty("addresses").EnumerateArray())
+ {
+ IPAddress address = IPAddress.Parse(jsonAddress.GetString());
- if (address.AddressFamily == AddressFamily.InterNetworkV6)
- answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
- }
- break;
- }
+ if (address.AddressFamily == AddressFamily.InterNetworkV6)
+ answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
+ }
+ break;
+ }
- if (answers.Count == 0)
- return Task.FromResult(null);
+ if (answers.Count == 0)
+ return Task.FromResult(null);
- if (answers.Count > 1)
- answers.Shuffle();
+ if (answers.Count > 1)
+ answers.Shuffle();
- EDnsOption[] options;
+ EDnsOption[] options;
- if (requestECS is null)
- {
- options = null;
- }
- else
- {
- if (ecsUsed)
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.AddressValue);
+ if (requestECS is null)
+ {
+ options = null;
+ }
else
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.AddressValue);
- }
+ {
+ if (ecsUsed)
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.Address);
+ else
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.Address);
+ }
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
+ }
default:
return Task.FromResult(null);
diff --git a/Apps/GeoDistanceApp/CNAME.cs b/Apps/GeoDistanceApp/CNAME.cs
index ca3c7783..70b78013 100644
--- a/Apps/GeoDistanceApp/CNAME.cs
+++ b/Apps/GeoDistanceApp/CNAME.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,11 +20,12 @@ along with this program. If not, see .
using DnsServerCore.ApplicationCommon;
using MaxMind.GeoIP2.Model;
using MaxMind.GeoIP2.Responses;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.EDnsOptions;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
@@ -108,21 +109,23 @@ namespace GeoDistance
if ((location is null) && _maxMind.DatabaseReader.TryCity(remoteEP.Address, out CityResponse response) && response.Location.HasCoordinates)
location = response.Location;
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
- dynamic jsonClosestServer = null;
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ JsonElement jsonClosestServer = default;
if (location is null)
{
- jsonClosestServer = jsonAppRecordData[0];
+ if (jsonAppRecordData.GetArrayLength() > 0)
+ jsonClosestServer = jsonAppRecordData[0];
}
else
{
double lastDistance = double.MaxValue;
- foreach (dynamic jsonServer in jsonAppRecordData)
+ foreach (JsonElement jsonServer in jsonAppRecordData.EnumerateArray())
{
- double lat = Convert.ToDouble(jsonServer.lat.Value);
- double @long = Convert.ToDouble(jsonServer.@long.Value);
+ double lat = Convert.ToDouble(jsonServer.GetProperty("lat").GetString());
+ double @long = Convert.ToDouble(jsonServer.GetProperty("long").GetString());
double distance = GetDistance(lat, @long, location.Latitude.Value, location.Longitude.Value);
@@ -134,14 +137,10 @@ namespace GeoDistance
}
}
- if (jsonClosestServer is null)
+ if (jsonClosestServer.ValueKind == JsonValueKind.Undefined)
return Task.FromResult(null);
- dynamic jsonCname = jsonClosestServer.cname;
- if (jsonCname is null)
- return Task.FromResult(null);
-
- string cname = jsonCname.Value;
+ string cname = jsonClosestServer.GetPropertyValue("cname", null);
if (string.IsNullOrEmpty(cname))
return Task.FromResult(null);
@@ -161,12 +160,12 @@ namespace GeoDistance
else
{
if (ecsUsed)
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.AddressValue);
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.Address);
else
- options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.AddressValue);
+ options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.Address);
}
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
}
#endregion
diff --git a/Apps/GeoDistanceApp/GeoDistanceApp.csproj b/Apps/GeoDistanceApp/GeoDistanceApp.csproj
index a6967219..65b478e1 100644
--- a/Apps/GeoDistanceApp/GeoDistanceApp.csproj
+++ b/Apps/GeoDistanceApp/GeoDistanceApp.csproj
@@ -4,7 +4,7 @@
net7.0
false
true
- 5.0.1
+ 6.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -12,14 +12,13 @@
GeoDistance
https://technitium.com/dns/
https://github.com/TechnitiumSoftware/DnsServer
- Allows creating APP records in a primary zone that can return A or AAAA records, or CNAME record of the server located geographically closest to the client using MaxMind GeoIP2 City database. Supports EDNS Client Subnet (ECS). This app requires MaxMind GeoIP2 database and includes the GeoLite2 version for trial. \n\nTo update the MaxMind GeoIP2 database for your app, download the GeoIP2-City.mmdb file from MaxMind and zip it. Use the zip file with the manual Update option.
+ Allows creating APP records in a primary and forwarder zones that can return A or AAAA records, or CNAME record of the server located geographically closest to the client using MaxMind GeoIP2 City database. Supports EDNS Client Subnet (ECS). This app requires MaxMind GeoIP2 database and includes the GeoLite2 version for trial. \n\nTo update the MaxMind GeoIP2 database for your app, download the GeoIP2-City.mmdb file from MaxMind and zip it. Use the zip file with the manual Update option.
false
Library
-
diff --git a/Apps/NoDataApp/App.cs b/Apps/NoDataApp/App.cs
index 09f4584c..7c907473 100644
--- a/Apps/NoDataApp/App.cs
+++ b/Apps/NoDataApp/App.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,9 +18,9 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
@@ -52,13 +52,14 @@ namespace NoData
if (question.Name.Equals(appRecordName, StringComparison.OrdinalIgnoreCase))
{
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
- foreach (dynamic jsonBlockedType in jsonAppRecordData.blockedTypes)
+ foreach (JsonElement jsonBlockedType in jsonAppRecordData.GetProperty("blockedTypes").EnumerateArray())
{
- DnsResourceRecordType blockedType = Enum.Parse(jsonBlockedType.Value, true);
+ DnsResourceRecordType blockedType = Enum.Parse(jsonBlockedType.GetString(), true);
if ((blockedType == question.Type) || (blockedType == DnsResourceRecordType.ANY))
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question));
}
}
diff --git a/Apps/NoDataApp/NoDataApp.csproj b/Apps/NoDataApp/NoDataApp.csproj
index 017e7cdc..8f867a97 100644
--- a/Apps/NoDataApp/NoDataApp.csproj
+++ b/Apps/NoDataApp/NoDataApp.csproj
@@ -3,7 +3,7 @@
net7.0
false
- 1.0.1
+ 2.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -16,10 +16,6 @@
Library
-
-
-
-
false
diff --git a/Apps/NxDomainApp/App.cs b/Apps/NxDomainApp/App.cs
index 51c52cb7..86d8f64b 100644
--- a/Apps/NxDomainApp/App.cs
+++ b/Apps/NxDomainApp/App.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,10 +18,12 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
+using System;
using System.Collections.Generic;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
@@ -51,16 +53,6 @@ namespace NxDomain
#region private
- private static IReadOnlyDictionary ReadJsonDomainArray(dynamic jsonDomainArray)
- {
- Dictionary domains = new Dictionary(jsonDomainArray.Count);
-
- foreach (dynamic jsonDomain in jsonDomainArray)
- domains.TryAdd(jsonDomain.Value, null);
-
- return domains;
- }
-
private static string GetParentZone(string domain)
{
int i = domain.IndexOf('.');
@@ -100,18 +92,21 @@ namespace NxDomain
{
_soaRecord = new DnsSOARecordData(dnsServer.ServerDomain, "hostadmin@" + dnsServer.ServerDomain, 1, 14400, 3600, 604800, 60);
- dynamic jsonConfig = JsonConvert.DeserializeObject(config);
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
- _enableBlocking = jsonConfig.enableBlocking.Value;
- _allowTxtBlockingReport = jsonConfig.allowTxtBlockingReport.Value;
-
- _blockListZone = ReadJsonDomainArray(jsonConfig.blocked);
+ _enableBlocking = jsonConfig.GetProperty("enableBlocking").GetBoolean();
+ _allowTxtBlockingReport = jsonConfig.GetProperty("allowTxtBlockingReport").GetBoolean();
+ _blockListZone = jsonConfig.ReadArrayAsMap("blocked", delegate (JsonElement jsonDomainName) { return new Tuple(jsonDomainName.GetString(), null); });
return Task.CompletedTask;
}
public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
{
+ if (!_enableBlocking)
+ return Task.FromResult(null);
+
DnsQuestionRecord question = request.Question[0];
if (!IsZoneBlocked(question.Name, out string blockedDomain))
@@ -122,7 +117,7 @@ namespace NxDomain
//return meta data
DnsResourceRecord[] answer = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, 60, new DnsTXTRecordData("source=nx-domain-app; domain=" + blockedDomain)) };
- return Task.FromResult(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answer) { Tag = DnsServerResponseType.Blocked });
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answer) { Tag = DnsServerResponseType.Blocked });
}
else
{
@@ -132,7 +127,7 @@ namespace NxDomain
IReadOnlyList authority = new DnsResourceRecord[] { new DnsResourceRecord(parentDomain, DnsResourceRecordType.SOA, question.Class, 60, _soaRecord) };
- return Task.FromResult(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NxDomain, request.Question, null, authority) { Tag = DnsServerResponseType.Blocked });
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NxDomain, request.Question, null, authority) { Tag = DnsServerResponseType.Blocked });
}
}
diff --git a/Apps/NxDomainApp/NxDomainApp.csproj b/Apps/NxDomainApp/NxDomainApp.csproj
index 8e972fdd..d3cf276e 100644
--- a/Apps/NxDomainApp/NxDomainApp.csproj
+++ b/Apps/NxDomainApp/NxDomainApp.csproj
@@ -3,7 +3,7 @@
net7.0
false
- 3.0.1
+ 4.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -16,10 +16,6 @@
Library
-
-
-
-
false
@@ -27,6 +23,10 @@
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll
+ false
+
..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll
false
diff --git a/Apps/QueryLogsSqliteApp/App.cs b/Apps/QueryLogsSqliteApp/App.cs
index f59e7186..dab8ddcd 100644
--- a/Apps/QueryLogsSqliteApp/App.cs
+++ b/Apps/QueryLogsSqliteApp/App.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,12 +19,12 @@ along with this program. If not, see .
using DnsServerCore.ApplicationCommon;
using Microsoft.Data.Sqlite;
-using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using TechnitiumLibrary.Net.Dns;
@@ -192,13 +192,14 @@ namespace QueryLogsSqlite
{
_dnsServer = dnsServer;
- dynamic jsonConfig = JsonConvert.DeserializeObject(config);
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
- _enableLogging = jsonConfig.enableLogging.Value;
- _maxLogDays = Convert.ToInt32(jsonConfig.maxLogDays.Value);
+ _enableLogging = jsonConfig.GetProperty("enableLogging").GetBoolean();
+ _maxLogDays = jsonConfig.GetProperty("maxLogDays").GetInt32();
- string sqliteDbPath = jsonConfig.sqliteDbPath.Value;
- string connectionString = jsonConfig.connectionString.Value;
+ string sqliteDbPath = jsonConfig.GetProperty("sqliteDbPath").GetString();
+ string connectionString = jsonConfig.GetProperty("connectionString").GetString();
if (!Path.IsPathRooted(sqliteDbPath))
sqliteDbPath = Path.Combine(_dnsServer.ApplicationFolder, sqliteDbPath);
@@ -393,9 +394,7 @@ CREATE TABLE IF NOT EXISTS dns_logs
public Task QueryLogsAsync(long pageNumber, int entriesPerPage, bool descendingOrder, DateTime? start, DateTime? end, IPAddress clientIpAddress, DnsTransportProtocol? protocol, DnsServerResponseType? responseType, DnsResponseCode? rcode, string qname, DnsResourceRecordType? qtype, DnsClass? qclass)
{
- if (pageNumber < 0)
- pageNumber = long.MaxValue;
- else if (pageNumber == 0)
+ if (pageNumber == 0)
pageNumber = 1;
if (qname is not null)
@@ -486,7 +485,7 @@ CREATE TABLE IF NOT EXISTS dns_logs
long totalPages = (totalEntries / entriesPerPage) + (totalEntries % entriesPerPage > 0 ? 1 : 0);
- if (pageNumber > totalPages)
+ if ((pageNumber > totalPages) || (pageNumber < 0))
pageNumber = totalPages;
long endRowNum;
diff --git a/Apps/QueryLogsSqliteApp/QueryLogsSqliteApp.csproj b/Apps/QueryLogsSqliteApp/QueryLogsSqliteApp.csproj
index 7c177cc3..61b75dd5 100644
--- a/Apps/QueryLogsSqliteApp/QueryLogsSqliteApp.csproj
+++ b/Apps/QueryLogsSqliteApp/QueryLogsSqliteApp.csproj
@@ -4,7 +4,7 @@
net7.0
false
true
- 3.1
+ 4.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -18,8 +18,7 @@
-
-
+
diff --git a/Apps/SplitHorizonApp/AddressTranslation.cs b/Apps/SplitHorizonApp/AddressTranslation.cs
index 3491e1fb..8bcc9715 100644
--- a/Apps/SplitHorizonApp/AddressTranslation.cs
+++ b/Apps/SplitHorizonApp/AddressTranslation.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,12 +18,13 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
+using TechnitiumLibrary;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
@@ -106,15 +107,18 @@ namespace SplitHorizon
await File.WriteAllTextAsync(Path.Combine(dnsServer.ApplicationFolder, "dnsApp.config"), config);
}
- dynamic jsonConfig = JsonConvert.DeserializeObject(config);
-
- if (jsonConfig.enableAddressTranslation is null)
+ do
{
- //update old config with default config
- config = config.TrimEnd(' ', '\t', '\r', '\n');
- config = config.Substring(0, config.Length - 1);
- config = config.TrimEnd(' ', '\t', '\r', '\n');
- config += """
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
+
+ if (!jsonConfig.TryGetProperty("enableAddressTranslation", out _))
+ {
+ //update old config with default config
+ config = config.TrimEnd(' ', '\t', '\r', '\n');
+ config = config.Substring(0, config.Length - 1);
+ config = config.TrimEnd(' ', '\t', '\r', '\n');
+ config += """
,
"enableAddressTranslation": false,
"networkGroupMap": {
@@ -153,43 +157,31 @@ namespace SplitHorizon
]
}
""";
- await File.WriteAllTextAsync(Path.Combine(dnsServer.ApplicationFolder, "dnsApp.config"), config);
+ await File.WriteAllTextAsync(Path.Combine(dnsServer.ApplicationFolder, "dnsApp.config"), config);
- jsonConfig = JsonConvert.DeserializeObject(config);
- }
-
- _enableAddressTranslation = jsonConfig.enableAddressTranslation.Value;
-
- {
- Dictionary networkGroupMap = new Dictionary();
-
- foreach (dynamic jsonProperty in jsonConfig.networkGroupMap)
- {
- string network = jsonProperty.Name;
- string group = jsonProperty.Value;
-
- if (!NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
- throw new InvalidOperationException("Network group map contains an invalid network address: " + network);
-
- networkGroupMap.Add(networkAddress, group);
+ //reparse config
+ continue;
}
- _networkGroupMap = networkGroupMap;
- }
+ _enableAddressTranslation = jsonConfig.GetProperty("enableAddressTranslation").GetBoolean();
- {
- Dictionary groups = new Dictionary();
+ _networkGroupMap = jsonConfig.ReadObjectAsMap("networkGroupMap", delegate (string strNetworkAddress, JsonElement jsonGroupName)
+ {
+ if (!NetworkAddress.TryParse(strNetworkAddress, out NetworkAddress networkAddress))
+ throw new InvalidOperationException("Network group map contains an invalid network address: " + strNetworkAddress);
- foreach (dynamic jsonGroup in jsonConfig.groups)
+ return new Tuple(networkAddress, jsonGroupName.GetString());
+ });
+
+ _groups = jsonConfig.ReadArrayAsMap("groups", delegate (JsonElement jsonGroup)
{
Group group = new Group(jsonGroup);
- groups.Add(group.Name, group);
- }
+ return new Tuple(group.Name, group);
+ });
- _groups = groups;
+ break;
}
-
- return;
+ while (true);
}
public Task PostProcessAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, DnsDatagram response)
@@ -245,7 +237,7 @@ namespace SplitHorizon
IPAddress externalIp = (answer.RDATA as DnsARecordData).Address;
if (group.ExternalToInternalTranslation.TryGetValue(externalIp, out IPAddress internalIp))
- newAnswer.Add(new DnsResourceRecord(answer.Name, answer.Type, answer.Class, answer.TtlValue, new DnsARecordData(internalIp)));
+ newAnswer.Add(new DnsResourceRecord(answer.Name, answer.Type, answer.Class, answer.TTL, new DnsARecordData(internalIp)));
else
newAnswer.Add(answer);
}
@@ -256,7 +248,7 @@ namespace SplitHorizon
IPAddress externalIp = (answer.RDATA as DnsAAAARecordData).Address;
if (group.ExternalToInternalTranslation.TryGetValue(externalIp, out IPAddress internalIp))
- newAnswer.Add(new DnsResourceRecord(answer.Name, answer.Type, answer.Class, answer.TtlValue, new DnsAAAARecordData(internalIp)));
+ newAnswer.Add(new DnsResourceRecord(answer.Name, answer.Type, answer.Class, answer.TTL, new DnsAAAARecordData(internalIp)));
else
newAnswer.Add(answer);
}
@@ -299,7 +291,7 @@ namespace SplitHorizon
if ((groupName is null) || !_groups.TryGetValue(groupName, out Group group) || !group.Enabled || !group.TranslateReverseLookups)
return Task.FromResult(null);
- IPAddress ptrIpAddress = IPAddressExtension.ParseReverseDomain(question.Name);
+ IPAddress ptrIpAddress = IPAddressExtensions.ParseReverseDomain(question.Name);
if (!group.InternalToExternalTranslation.TryGetValue(ptrIpAddress, out IPAddress externalIp))
return Task.FromResult(null);
@@ -332,21 +324,23 @@ namespace SplitHorizon
#region constructor
- public Group(dynamic jsonGroup)
+ public Group(JsonElement jsonGroup)
{
- _name = jsonGroup.name.Value;
- _enabled = jsonGroup.enabled.Value;
- _translateReverseLookups = jsonGroup.translateReverseLookups.Value;
+ _name = jsonGroup.GetProperty("name").GetString();
+ _enabled = jsonGroup.GetProperty("enabled").GetBoolean();
+ _translateReverseLookups = jsonGroup.GetProperty("translateReverseLookups").GetBoolean();
+
+ JsonElement jsonExternalToInternalTranslation = jsonGroup.GetProperty("externalToInternalTranslation");
if (_translateReverseLookups)
{
Dictionary externalToInternalTranslation = new Dictionary();
Dictionary internalToExternalTranslation = new Dictionary();
- foreach (dynamic jsonProperty in jsonGroup.externalToInternalTranslation)
+ foreach (JsonProperty jsonProperty in jsonExternalToInternalTranslation.EnumerateObject())
{
string strExternalIp = jsonProperty.Name;
- string strInternalIp = jsonProperty.Value;
+ string strInternalIp = jsonProperty.Value.GetString();
IPAddress externalIp = IPAddress.Parse(strExternalIp);
IPAddress internalIp = IPAddress.Parse(strInternalIp);
@@ -362,10 +356,10 @@ namespace SplitHorizon
{
Dictionary externalToInternalTranslation = new Dictionary();
- foreach (dynamic jsonProperty in jsonGroup.externalToInternalTranslation)
+ foreach (JsonProperty jsonProperty in jsonExternalToInternalTranslation.EnumerateObject())
{
string strExternalIp = jsonProperty.Name;
- string strInternalIp = jsonProperty.Value;
+ string strInternalIp = jsonProperty.Value.GetString();
IPAddress externalIp = IPAddress.Parse(strExternalIp);
IPAddress internalIp = IPAddress.Parse(strInternalIp);
diff --git a/Apps/SplitHorizonApp/SimpleAddress.cs b/Apps/SplitHorizonApp/SimpleAddress.cs
index be1e5c62..11c84f44 100644
--- a/Apps/SplitHorizonApp/SimpleAddress.cs
+++ b/Apps/SplitHorizonApp/SimpleAddress.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,11 +18,11 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary;
using TechnitiumLibrary.Net;
@@ -105,28 +105,24 @@ namespace SplitHorizon
await File.WriteAllTextAsync(Path.Combine(dnsServer.ApplicationFolder, "dnsApp.config"), config);
}
- dynamic jsonConfig = JsonConvert.DeserializeObject(config);
+ using JsonDocument jsonDocument = JsonDocument.Parse(config);
+ JsonElement jsonConfig = jsonDocument.RootElement;
- dynamic jsonNetworks = jsonConfig.networks;
- if (jsonNetworks is null)
- {
- _networks = new Dictionary>(1);
- }
- else
+ if (jsonConfig.TryGetProperty("networks", out JsonElement jsonNetworks))
{
Dictionary> networks = new Dictionary>();
- foreach (dynamic jsonProperty in jsonNetworks)
+ foreach (JsonProperty jsonProperty in jsonNetworks.EnumerateObject())
{
string networkName = jsonProperty.Name;
- dynamic jsonNetworkAddresses = jsonProperty.Value;
- if (jsonNetworkAddresses is not null)
+ JsonElement jsonNetworkAddresses = jsonProperty.Value;
+ if (jsonNetworkAddresses.ValueKind == JsonValueKind.Array)
{
- List networkAddresses = new List();
+ List networkAddresses = new List(jsonNetworkAddresses.GetArrayLength());
- foreach (dynamic jsonNetworkAddress in jsonNetworkAddresses)
- networkAddresses.Add(NetworkAddress.Parse(jsonNetworkAddress.Value));
+ foreach (JsonElement jsonNetworkAddress in jsonNetworkAddresses.EnumerateArray())
+ networkAddresses.Add(NetworkAddress.Parse(jsonNetworkAddress.GetString()));
networks.TryAdd(networkName, networkAddresses);
}
@@ -134,6 +130,10 @@ namespace SplitHorizon
_networks = networks;
}
+ else
+ {
+ _networks = new Dictionary>(1);
+ }
}
public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
@@ -143,82 +143,88 @@ namespace SplitHorizon
{
case DnsResourceRecordType.A:
case DnsResourceRecordType.AAAA:
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
- dynamic jsonAddresses = null;
-
- NetworkAddress selectedNetwork = null;
-
- foreach (dynamic jsonProperty in jsonAppRecordData)
+ using (JsonDocument jsonDocument = JsonDocument.Parse(appRecordData))
{
- string name = jsonProperty.Name;
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ JsonElement jsonAddresses = default;
- if ((name == "public") || (name == "private"))
- continue;
+ NetworkAddress selectedNetwork = null;
- if (_networks.TryGetValue(name, out List networkAddresses))
+ foreach (JsonProperty jsonProperty in jsonAppRecordData.EnumerateObject())
{
- foreach (NetworkAddress networkAddress in networkAddresses)
+ string name = jsonProperty.Name;
+
+ if ((name == "public") || (name == "private"))
+ continue;
+
+ if (_networks.TryGetValue(name, out List networkAddresses))
{
- if (networkAddress.Contains(remoteEP.Address))
+ foreach (NetworkAddress networkAddress in networkAddresses)
{
- jsonAddresses = jsonProperty.Value;
+ if (networkAddress.Contains(remoteEP.Address))
+ {
+ jsonAddresses = jsonProperty.Value;
+ break;
+ }
+ }
+
+ if (jsonAddresses.ValueKind != JsonValueKind.Undefined)
break;
+ }
+ else if (NetworkAddress.TryParse(name, out NetworkAddress networkAddress))
+ {
+ if (networkAddress.Contains(remoteEP.Address) && ((selectedNetwork is null) || (networkAddress.PrefixLength > selectedNetwork.PrefixLength)))
+ {
+ selectedNetwork = networkAddress;
+ jsonAddresses = jsonProperty.Value;
}
}
+ }
- if (jsonAddresses is not null)
+ if (jsonAddresses.ValueKind == JsonValueKind.Undefined)
+ {
+ if (NetUtilities.IsPrivateIP(remoteEP.Address))
+ {
+ if (!jsonAppRecordData.TryGetProperty("private", out jsonAddresses))
+ return Task.FromResult(null);
+ }
+ else
+ {
+ if (!jsonAppRecordData.TryGetProperty("public", out jsonAddresses))
+ return Task.FromResult(null);
+ }
+ }
+
+ List answers = new List();
+
+ switch (question.Type)
+ {
+ case DnsResourceRecordType.A:
+ foreach (JsonElement jsonAddress in jsonAddresses.EnumerateArray())
+ {
+ if (IPAddress.TryParse(jsonAddress.GetString(), out IPAddress address) && (address.AddressFamily == AddressFamily.InterNetwork))
+ answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
+ }
+ break;
+
+ case DnsResourceRecordType.AAAA:
+ foreach (JsonElement jsonAddress in jsonAddresses.EnumerateArray())
+ {
+ if (IPAddress.TryParse(jsonAddress.GetString(), out IPAddress address) && (address.AddressFamily == AddressFamily.InterNetworkV6))
+ answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
+ }
break;
}
- else if (NetworkAddress.TryParse(name, out NetworkAddress networkAddress))
- {
- if (networkAddress.Contains(remoteEP.Address) && ((selectedNetwork is null) || (networkAddress.PrefixLength > selectedNetwork.PrefixLength)))
- {
- selectedNetwork = networkAddress;
- jsonAddresses = jsonProperty.Value;
- }
- }
- }
- if (jsonAddresses is null)
- {
- if (NetUtilities.IsPrivateIP(remoteEP.Address))
- jsonAddresses = jsonAppRecordData.@private;
- else
- jsonAddresses = jsonAppRecordData.@public;
-
- if (jsonAddresses is null)
+ if (answers.Count == 0)
return Task.FromResult(null);
+
+ if (answers.Count > 1)
+ answers.Shuffle();
+
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers));
}
- List answers = new List();
-
- switch (question.Type)
- {
- case DnsResourceRecordType.A:
- foreach (dynamic jsonAddress in jsonAddresses)
- {
- if (IPAddress.TryParse(jsonAddress.Value, out IPAddress address) && (address.AddressFamily == AddressFamily.InterNetwork))
- answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
- }
- break;
-
- case DnsResourceRecordType.AAAA:
- foreach (dynamic jsonAddress in jsonAddresses)
- {
- if (IPAddress.TryParse(jsonAddress.Value, out IPAddress address) && (address.AddressFamily == AddressFamily.InterNetworkV6))
- answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
- }
- break;
- }
-
- if (answers.Count == 0)
- return Task.FromResult(null);
-
- if (answers.Count > 1)
- answers.Shuffle();
-
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers));
-
default:
return Task.FromResult(null);
}
@@ -240,15 +246,15 @@ namespace SplitHorizon
{
return @"{
""public"": [
- ""1.1.1.1"",
+ ""1.1.1.1"",
""2.2.2.2""
],
""private"": [
- ""192.168.1.1"",
+ ""192.168.1.1"",
""::1""
],
""custom-networks"": [
- ""172.16.1.1"",
+ ""172.16.1.1""
],
""10.0.0.0/8"": [
""10.1.1.1""
diff --git a/Apps/SplitHorizonApp/SimpleCNAME.cs b/Apps/SplitHorizonApp/SimpleCNAME.cs
index f7e62db0..46d1d4fe 100644
--- a/Apps/SplitHorizonApp/SimpleCNAME.cs
+++ b/Apps/SplitHorizonApp/SimpleCNAME.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,10 +18,10 @@ along with this program. If not, see .
*/
using DnsServerCore.ApplicationCommon;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
+using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns;
@@ -50,12 +50,13 @@ namespace SplitHorizon
public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
{
- dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
- dynamic jsonCname = null;
+ using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
+ JsonElement jsonAppRecordData = jsonDocument.RootElement;
+ JsonElement jsonCname = default;
NetworkAddress selectedNetwork = null;
- foreach (dynamic jsonProperty in jsonAppRecordData)
+ foreach (JsonProperty jsonProperty in jsonAppRecordData.EnumerateObject())
{
string name = jsonProperty.Name;
@@ -73,7 +74,7 @@ namespace SplitHorizon
}
}
- if (jsonCname is not null)
+ if (jsonCname.ValueKind != JsonValueKind.Undefined)
break;
}
else if (NetworkAddress.TryParse(name, out NetworkAddress networkAddress))
@@ -86,18 +87,21 @@ namespace SplitHorizon
}
}
- if (jsonCname is null)
+ if (jsonCname.ValueKind == JsonValueKind.Undefined)
{
if (NetUtilities.IsPrivateIP(remoteEP.Address))
- jsonCname = jsonAppRecordData.@private;
+ {
+ if (!jsonAppRecordData.TryGetProperty("private", out jsonCname))
+ return Task.FromResult(null);
+ }
else
- jsonCname = jsonAppRecordData.@public;
-
- if (jsonCname is null)
- return Task.FromResult(null);
+ {
+ if (!jsonAppRecordData.TryGetProperty("public", out jsonCname))
+ return Task.FromResult(null);
+ }
}
- string cname = jsonCname.Value;
+ string cname = jsonCname.GetString();
if (string.IsNullOrEmpty(cname))
return Task.FromResult(null);
@@ -109,7 +113,7 @@ namespace SplitHorizon
else
answers = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, DnsClass.IN, appRecordTtl, new DnsCNAMERecordData(cname)) };
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers));
}
#endregion
diff --git a/Apps/SplitHorizonApp/SplitHorizonApp.csproj b/Apps/SplitHorizonApp/SplitHorizonApp.csproj
index 35eff512..07111f5f 100644
--- a/Apps/SplitHorizonApp/SplitHorizonApp.csproj
+++ b/Apps/SplitHorizonApp/SplitHorizonApp.csproj
@@ -3,7 +3,7 @@
net7.0
false
- 5.0.1
+ 6.0
Technitium
Technitium DNS Server
Shreyas Zare
@@ -11,15 +11,11 @@
SplitHorizon
https://technitium.com/dns/
https://github.com/TechnitiumSoftware/DnsServer
- Allows creating APP records in a primary zone that can return different set of A or AAAA records, or CNAME record for clients querying over public, private, or other specified networks.\n\nEnables Address Translation of IP addresses in a DNS response for A & AAAA type request based on the client's network address and the configured 1:1 translation.
+ Allows creating APP records in a primary and forwarder zones that can return different set of A or AAAA records, or CNAME record for clients querying over public, private, or other specified networks.\n\nEnables Address Translation of IP addresses in a DNS response for A & AAAA type request based on the client's network address and the configured 1:1 translation.
false
Library
-
-
-
-
false
diff --git a/Apps/WhatIsMyDnsApp/App.cs b/Apps/WhatIsMyDnsApp/App.cs
index ba4f9b23..3b0c02aa 100644
--- a/Apps/WhatIsMyDnsApp/App.cs
+++ b/Apps/WhatIsMyDnsApp/App.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -73,7 +73,7 @@ namespace WhatIsMyDns
return Task.FromResult(null);
}
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { answer }));
+ return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { answer }));
}
#endregion
diff --git a/Apps/WhatIsMyDnsApp/WhatIsMyDnsApp.csproj b/Apps/WhatIsMyDnsApp/WhatIsMyDnsApp.csproj
index c193dc97..12682d73 100644
--- a/Apps/WhatIsMyDnsApp/WhatIsMyDnsApp.csproj
+++ b/Apps/WhatIsMyDnsApp/WhatIsMyDnsApp.csproj
@@ -4,7 +4,7 @@
net7.0
false
true
- 5.0
+ 5.0.1
Technitium
Technitium DNS Server
Shreyas Zare
@@ -12,7 +12,7 @@
WhatIsMyDns
https://technitium.com/dns/
https://github.com/TechnitiumSoftware/DnsServer
- Allows creating APP records in a primary zone that can return the IP address of the user's DNS Server for A, AAAA, and TXT queries.
+ Allows creating APP records in a primary and forwarder zones that can return the IP address of the user's DNS Server for A, AAAA, and TXT queries.
false
Library
diff --git a/Apps/WildIpApp/App.cs b/Apps/WildIpApp/App.cs
index 274a22bc..900c8c1d 100644
--- a/Apps/WildIpApp/App.cs
+++ b/Apps/WildIpApp/App.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -28,6 +28,12 @@ namespace WildIp
{
public class App : IDnsApplication, IDnsAppRecordRequestHandler
{
+ #region variables
+
+ IDnsServer _dnsServer;
+
+ #endregion
+
#region IDisposable
public void Dispose()
@@ -41,18 +47,19 @@ namespace WildIp
public Task InitializeAsync(IDnsServer dnsServer, string config)
{
- //do nothing
+ _dnsServer = dnsServer;
+
return Task.CompletedTask;
}
- public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
+ public async Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
{
string qname = request.Question[0].Name;
if (qname.Length == appRecordName.Length)
- return Task.FromResult(null);
+ return null;
- DnsResourceRecord answer;
+ DnsResourceRecord answer = null;
switch (request.Question[0].Type)
{
@@ -69,12 +76,8 @@ namespace WildIp
rawIp[i++] = x;
}
- if (i < 4)
- return Task.FromResult(null);
-
- IPAddress address = new IPAddress(rawIp);
-
- answer = new DnsResourceRecord(request.Question[0].Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address));
+ if (i == 4)
+ answer = new DnsResourceRecord(request.Question[0].Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(new IPAddress(rawIp)));
}
break;
@@ -90,18 +93,21 @@ namespace WildIp
break;
}
- if (address is null)
- return Task.FromResult(null);
-
- answer = new DnsResourceRecord(request.Question[0].Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address));
+ if (address is not null)
+ answer = new DnsResourceRecord(request.Question[0].Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address));
}
break;
-
- default:
- return Task.FromResult(null);
}
- return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { answer }));
+ if (answer is null)
+ {
+ //NODATA reponse
+ DnsDatagram soaResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(zoneName, DnsResourceRecordType.SOA, DnsClass.IN));
+
+ return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, soaResponse.Answer);
+ }
+
+ return new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { answer });
}
#endregion
diff --git a/Apps/WildIpApp/WildIpApp.csproj b/Apps/WildIpApp/WildIpApp.csproj
index 573ef1ab..00afeca9 100644
--- a/Apps/WildIpApp/WildIpApp.csproj
+++ b/Apps/WildIpApp/WildIpApp.csproj
@@ -4,7 +4,7 @@
net7.0
false
true
- 2.0
+ 2.1
Technitium
Technitium DNS Server
Shreyas Zare
@@ -12,7 +12,7 @@
WildIp
https://technitium.com/dns/
https://github.com/TechnitiumSoftware/DnsServer
- Allows creating APP records in a primary zone that can return the IP address embedded in the subdomain name for A and AAAA queries. It works similar to sslip.io.
+ Allows creating APP records in a primary and forwarder zones that can return the IP address embedded in the subdomain name for A and AAAA queries. It works similar to sslip.io.
false
Library
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a23c2faf..ff7a2177 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
# Technitium DNS Server Change Log
+## Version 11.0
+Release Date: 18 February 2022
+
+- Added support for DNS-over-QUIC (DoQ) [RFC 9250](https://www.ietf.org/rfc/rfc9250.html). This allows you to run DoQ service as well as use it with Forwarders. DoQ implementation supports running over SOCKS5 proxy server that provides UDP transport.
+- Added support for Zone Transfer over QUIC (XFR-over-QUIC) [RFC 9250](https://www.ietf.org/rfc/rfc9250.html).
+- Updated DNS-over-HTTPS protocol implementation to support HTTP/2 and HTTP/3. DNS-over-HTTP/3 can be forced by using `h3` instead of `https` scheme for the URL.
+- Updated DNS server's web service backend to use Kestrel web server and thus the DNS server now requires ASP.NET Core Runtime to be installed. With this change, the web service now supports both HTTP/2 and HTTP/3 protocols. If you are using HTTP API, it is recommended to test your code/script with the new release.
+- Added support to save DNS cache data to disk on server shutdown and to reload it at startup.
+- Updated DNS server domain name blocking feature to support Extended DNS Errors to show report on the blocked domain name. With this support added, the DNS Client tab on the web panel will show blocking report for any blocked domain name.
+- Updated DNS server domain name blocking feature to support wildcard block lists file format and Adblock Plus file format.
+- Updated DNS server to detect when an upstream server blocks a domain name to reflect it in dashboard stats and query logs. It will now detect blocking signal from Quad9 and show Extended DNS Error for it.
+- Updated web panel Zones GUI to support pagination.
+- Advanced Blocking App: Updated DNS app to support wildcard block lists file format. Updated the app to disable CNAME cloaking when a domain name is allowed in config. Implemented Extended DNS Errors support to show blocked domain report.
+- Advanced Forwarding App: Added new DNS app to support bulk conditional forwarder.
+- DNS Block List App: Added new DNS app to allow running your own DNSBL or RBL block lists [RFC 5782](https://www.rfc-editor.org/rfc/rfc5782).
+- Added support for TFTP Server Address DHCP option (150).
+- Added support for Generic DHCP option to allow configuring option currently not supported by the DHCP server.
+- Removed support for non-standard DNS-over-HTTPS (JSON) protocol.
+- Removed Newtonsoft.Json dependency from the DNS server and all DNS apps.
+- Multiple other minor bug fixes and improvements.
+
## Version 10.0.1
Release Date: 4 December 2022
@@ -18,7 +39,7 @@ Release Date: 26 November 2022
- Implemented EDNS Client Subnet (ECS) [RFC 7871](https://datatracker.ietf.org/doc/html/rfc7871) support for recursive resolution and forwarding.
- Updated HTTP API to accept date time in ISO 8601 format for dashboard and query logs API calls. Any implementation that uses these API must test with new update before deploying to production.
- Upgraded codebase to .NET 7 runtime. If you had manually installed the DNS Server or .NET 6 Runtime earlier then you must install .NET 7 Runtime manually before upgrading the DNS server.
-- Fixed self-CNAME vulnerability reported by Xiang Li, [Network and Information Security Lab, Tsinghua University](https://netsec.ccert.edu.cn/) which caused the DNS server to follow CNAME in loop causing the answer to contain couple of hundred records before the loop limit was hit.
+- Fixed self-CNAME vulnerability [CVE-2022-48256] reported by Xiang Li, [Network and Information Security Lab, Tsinghua University](https://netsec.ccert.edu.cn/) which caused the DNS server to follow CNAME in loop causing the answer to contain couple of hundred records before the loop limit was hit.
- Updated DNS Apps framework with `IDnsPostProcessor` interface to allow manipulating outbound responses by DNS apps.
- NO DATA App: Added new app to allow returning NO DATA response in Conditional Forwarder zones to allow overriding existing records from the forwarder for specified record types.
- DNS64 App: Added new app to support DNS64 function [RFC 6147](https://www.rfc-editor.org/rfc/rfc6147) for use by IPv6 only clients.
diff --git a/DnsServer.sln b/DnsServer.sln
index 787db03d..57805a81 100644
--- a/DnsServer.sln
+++ b/DnsServer.sln
@@ -43,6 +43,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoDataApp", "Apps\NoDataApp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dns64App", "Apps\Dns64App\Dns64App.csproj", "{3514C4B4-78C1-46A1-82D5-4E676DD114FA}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DnsBlockListApp", "Apps\DnsBlockListApp\DnsBlockListApp.csproj", "{9F2EC41F-6A9E-47C4-B47B-75190D5B6903}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedForwardingApp", "Apps\AdvancedForwardingApp\AdvancedForwardingApp.csproj", "{42DD2C37-4082-4E33-9AB0-04A97290D5B7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -125,6 +129,14 @@ Global
{3514C4B4-78C1-46A1-82D5-4E676DD114FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3514C4B4-78C1-46A1-82D5-4E676DD114FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3514C4B4-78C1-46A1-82D5-4E676DD114FA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9F2EC41F-6A9E-47C4-B47B-75190D5B6903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9F2EC41F-6A9E-47C4-B47B-75190D5B6903}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9F2EC41F-6A9E-47C4-B47B-75190D5B6903}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9F2EC41F-6A9E-47C4-B47B-75190D5B6903}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42DD2C37-4082-4E33-9AB0-04A97290D5B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42DD2C37-4082-4E33-9AB0-04A97290D5B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42DD2C37-4082-4E33-9AB0-04A97290D5B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42DD2C37-4082-4E33-9AB0-04A97290D5B7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -144,6 +156,8 @@ Global
{8B6BEB00-0AC2-4680-A848-31AD8A0FCD82} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
{BE08D981-DDB0-4314-A571-D68EDF0F3971} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
{3514C4B4-78C1-46A1-82D5-4E676DD114FA} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
+ {9F2EC41F-6A9E-47C4-B47B-75190D5B6903} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
+ {42DD2C37-4082-4E33-9AB0-04A97290D5B7} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6747BB6D-2826-4356-A213-805FBCCF9201}
diff --git a/DnsServerApp/DnsServerApp.csproj b/DnsServerApp/DnsServerApp.csproj
index 295865a9..827cb031 100644
--- a/DnsServerApp/DnsServerApp.csproj
+++ b/DnsServerApp/DnsServerApp.csproj
@@ -6,7 +6,7 @@
Exe
net7.0
logo2.ico
- 10.0.1
+ 11.0
Technitium
Technitium DNS Server
Shreyas Zare
diff --git a/DnsServerApp/Program.cs b/DnsServerApp/Program.cs
index 0f7066a8..88359e8a 100644
--- a/DnsServerApp/Program.cs
+++ b/DnsServerApp/Program.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -69,7 +69,7 @@ namespace DnsServerApp
Console.WriteLine("Technitium DNS Server was started successfully.");
Console.WriteLine("Using config folder: " + service.ConfigFolder);
Console.WriteLine("");
- Console.WriteLine("Note: Open http://" + service.WebServiceHostname + ":" + service.WebServiceHttpPort + "/ in web browser to access web console.");
+ Console.WriteLine("Note: Open http://" + Environment.MachineName.ToLowerInvariant() + ":" + service.WebServiceHttpPort + "/ in web browser to access web console.");
Console.WriteLine("");
Console.WriteLine("Press [CTRL + C] to stop...");
diff --git a/DnsServerApp/install.sh b/DnsServerApp/install.sh
index f5c00748..5b7a1885 100644
--- a/DnsServerApp/install.sh
+++ b/DnsServerApp/install.sh
@@ -14,7 +14,7 @@ echo "==============================="
echo "Technitium DNS Server Installer"
echo "==============================="
-if dotnet --list-runtimes 2> /dev/null | grep -q "Microsoft.NETCore.App 7.0.";
+if dotnet --list-runtimes 2> /dev/null | grep -q "Microsoft.AspNetCore.App 7.0.";
then
dotnetFound="yes"
else
@@ -24,36 +24,36 @@ fi
if [ ! -d $dotnetDir ] && [ "$dotnetFound" = "yes" ]
then
echo ""
- echo ".NET 7 Runtime is already installed."
+ echo "ASP.NET Core Runtime is already installed."
else
echo ""
if [ -d $dotnetDir ] && [ "$dotnetFound" = "yes" ]
then
dotnetUpdate="yes"
- echo "Updating .NET 7 Runtime..."
+ echo "Updating ASP.NET Core Runtime..."
else
dotnetUpdate="no"
- echo "Installing .NET 7 Runtime..."
+ echo "Installing ASP.NET Core Runtime..."
fi
- curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -c 7.0 --runtime dotnet --no-path --install-dir $dotnetDir --verbose >> $installLog 2>&1
+ curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -c 7.0 --runtime aspnetcore --no-path --install-dir $dotnetDir --verbose >> $installLog 2>&1
if [ ! -f "/usr/bin/dotnet" ]
then
ln -s $dotnetDir/dotnet /usr/bin >> $installLog 2>&1
fi
- if dotnet --list-runtimes 2> /dev/null | grep -q "Microsoft.NETCore.App 7.0.";
+ if dotnet --list-runtimes 2> /dev/null | grep -q "Microsoft.AspNetCore.App 7.0.";
then
if [ "$dotnetUpdate" = "yes" ]
then
- echo ".NET 7 Runtime was updated successfully!"
+ echo "ASP.NET Core Runtime was updated successfully!"
else
- echo ".NET 7 Runtime was installed successfully!"
+ echo "ASP.NET Core Runtime was installed successfully!"
fi
else
- echo "Failed to install .NET 7 Runtime. Please try again."
+ echo "Failed to install ASP.NET Core Runtime. Please try again."
exit 1
fi
fi
diff --git a/DnsServerCore.ApplicationCommon/DnsServerCore.ApplicationCommon.csproj b/DnsServerCore.ApplicationCommon/DnsServerCore.ApplicationCommon.csproj
index 720dcf52..42f607a5 100644
--- a/DnsServerCore.ApplicationCommon/DnsServerCore.ApplicationCommon.csproj
+++ b/DnsServerCore.ApplicationCommon/DnsServerCore.ApplicationCommon.csproj
@@ -1,4 +1,4 @@
-
+
net7.0
@@ -11,7 +11,7 @@
DnsServerCore.ApplicationCommon
- 5.0
+ 5.1
diff --git a/DnsServerCore.ApplicationCommon/IDnsAppRecordRequestHandler.cs b/DnsServerCore.ApplicationCommon/IDnsAppRecordRequestHandler.cs
index 94cc93d6..b8f045e4 100644
--- a/DnsServerCore.ApplicationCommon/IDnsAppRecordRequestHandler.cs
+++ b/DnsServerCore.ApplicationCommon/IDnsAppRecordRequestHandler.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -39,7 +39,7 @@ namespace DnsServerCore.ApplicationCommon
/// The domain name of the APP record.
/// The TTL value set in the APP record.
/// The record data in the APP record as required for processing the request.
- /// The DNS response for the DNS request or null to send no answer response with an SOA authority.
+ /// The DNS response for the DNS request or null to send NODATA response when QNAME matches APP record name or else NXDOMAIN response with an SOA authority.
Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData);
///
diff --git a/DnsServerCore.ApplicationCommon/IDnsQueryLogger.cs b/DnsServerCore.ApplicationCommon/IDnsQueryLogger.cs
index 40ba6a59..dfc5145d 100644
--- a/DnsServerCore.ApplicationCommon/IDnsQueryLogger.cs
+++ b/DnsServerCore.ApplicationCommon/IDnsQueryLogger.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -31,7 +31,9 @@ namespace DnsServerCore.ApplicationCommon
Authoritative = 1,
Recursive = 2,
Cached = 3,
- Blocked = 4
+ Blocked = 4,
+ UpstreamBlocked = 5,
+ CacheBlocked = 6
}
///
diff --git a/DnsServerCore.ApplicationCommon/IDnsServer.cs b/DnsServerCore.ApplicationCommon/IDnsServer.cs
index 17b74faa..ead3ceeb 100644
--- a/DnsServerCore.ApplicationCommon/IDnsServer.cs
+++ b/DnsServerCore.ApplicationCommon/IDnsServer.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,10 +33,20 @@ namespace DnsServerCore.ApplicationCommon
/// Allows querying the DNS server core directly. This call supports recursion even if its not enabled in the DNS server configuration. The request wont be routed to any of the installed DNS Apps except for processing APP records. The request and its response are not counted in any stats or logged.
///
/// The question record containing the details to query.
+ /// The timeout value in milliseconds to wait for response.
/// The DNS response for the DNS query.
/// When request times out.
Task DirectQueryAsync(DnsQuestionRecord question, int timeout = 4000);
+ ///
+ /// Allows querying the DNS server core directly. This call supports recursion even if its not enabled in the DNS server configuration. The request wont be routed to any of the installed DNS Apps except for processing APP records. The request and its response are not counted in any stats or logged.
+ ///
+ /// The DNS request to query.
+ /// The timeout value in milliseconds to wait for response.
+ /// The DNS response for the DNS query.
+ /// When request times out.
+ Task DirectQueryAsync(DnsDatagram request, int timeout = 4000);
+
///
/// Writes a log entry to the DNS server log file.
///
diff --git a/DnsServerCore/Auth/User.cs b/DnsServerCore/Auth/User.cs
index e13c1f7e..1a8b37b1 100644
--- a/DnsServerCore/Auth/User.cs
+++ b/DnsServerCore/Auth/User.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -66,8 +66,8 @@ namespace DnsServerCore.Auth
public User(string displayName, string username, string password, int iterations = DEFAULT_ITERATIONS)
{
- DisplayName = displayName;
Username = username;
+ DisplayName = displayName;
ChangePassword(password, iterations);
@@ -92,9 +92,9 @@ namespace DnsServerCore.Auth
_sessionTimeoutSeconds = bR.ReadInt32();
_previousSessionLoggedOn = bR.ReadDateTime();
- _previousSessionRemoteAddress = IPAddressExtension.ReadFrom(bR);
+ _previousSessionRemoteAddress = IPAddressExtensions.ReadFrom(bR);
_recentSessionLoggedOn = bR.ReadDateTime();
- _recentSessionRemoteAddress = IPAddressExtension.ReadFrom(bR);
+ _recentSessionRemoteAddress = IPAddressExtensions.ReadFrom(bR);
{
int count = bR.ReadByte();
@@ -165,6 +165,9 @@ namespace DnsServerCore.Auth
public void LoggedInFrom(IPAddress remoteAddress)
{
+ if (remoteAddress.IsIPv4MappedToIPv6)
+ remoteAddress = remoteAddress.MapToIPv4();
+
_previousSessionLoggedOn = _recentSessionLoggedOn;
_previousSessionRemoteAddress = _recentSessionRemoteAddress;
@@ -220,9 +223,9 @@ namespace DnsServerCore.Auth
bW.Write(_sessionTimeoutSeconds);
bW.Write(_previousSessionLoggedOn);
- IPAddressExtension.WriteTo(_previousSessionRemoteAddress, bW);
+ IPAddressExtensions.WriteTo(_previousSessionRemoteAddress, bW);
bW.Write(_recentSessionLoggedOn);
- IPAddressExtension.WriteTo(_recentSessionRemoteAddress, bW);
+ IPAddressExtensions.WriteTo(_recentSessionRemoteAddress, bW);
bW.Write(Convert.ToByte(_memberOfGroups.Count));
diff --git a/DnsServerCore/Auth/UserSession.cs b/DnsServerCore/Auth/UserSession.cs
index 14d5d1aa..12f19b12 100644
--- a/DnsServerCore/Auth/UserSession.cs
+++ b/DnsServerCore/Auth/UserSession.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -56,6 +56,9 @@ namespace DnsServerCore.Auth
if ((tokenName is not null) && (tokenName.Length > 255))
throw new ArgumentOutOfRangeException(nameof(tokenName), "Token name length cannot exceed 255 characters.");
+ if (remoteAddress.IsIPv4MappedToIPv6)
+ remoteAddress = remoteAddress.MapToIPv4();
+
byte[] tokenBytes = new byte[32];
_rng.GetBytes(tokenBytes);
_token = Convert.ToHexString(tokenBytes).ToLower();
@@ -85,7 +88,7 @@ namespace DnsServerCore.Auth
_user = authManager.GetUser(bR.ReadShortString());
_lastSeen = bR.ReadDateTime();
- _lastSeenRemoteAddress = IPAddressExtension.ReadFrom(bR);
+ _lastSeenRemoteAddress = IPAddressExtensions.ReadFrom(bR);
_lastSeenUserAgent = bR.ReadShortString();
if (_lastSeenUserAgent.Length == 0)
@@ -104,6 +107,9 @@ namespace DnsServerCore.Auth
public void UpdateLastSeen(IPAddress remoteAddress, string lastSeenUserAgent)
{
+ if (remoteAddress.IsIPv4MappedToIPv6)
+ remoteAddress = remoteAddress.MapToIPv4();
+
_lastSeen = DateTime.UtcNow;
_lastSeenRemoteAddress = remoteAddress;
_lastSeenUserAgent = lastSeenUserAgent;
diff --git a/DnsServerCore/Dhcp/DhcpOption.cs b/DnsServerCore/Dhcp/DhcpOption.cs
index ea77a287..8792de38 100644
--- a/DnsServerCore/Dhcp/DhcpOption.cs
+++ b/DnsServerCore/Dhcp/DhcpOption.cs
@@ -19,6 +19,7 @@ along with this program. If not, see .
using DnsServerCore.Dhcp.Options;
using System;
+using System.Globalization;
using System.IO;
using TechnitiumLibrary.IO;
@@ -105,6 +106,7 @@ namespace DnsServerCore.Dhcp
DomainSearch = 119,
ClasslessStaticRoute = 121,
CAPWAPAccessControllerAddresses = 138,
+ TftpServerAddress = 150,
End = 255
}
@@ -119,6 +121,28 @@ namespace DnsServerCore.Dhcp
#region constructor
+ public DhcpOption(DhcpOptionCode code, string hexValue)
+ {
+ if (hexValue is null)
+ throw new ArgumentNullException(nameof(hexValue));
+
+ _code = code;
+
+ if (hexValue.Contains(':'))
+ _value = ParseColonHexString(hexValue);
+ else
+ _value = Convert.FromHexString(hexValue);
+ }
+
+ public DhcpOption(DhcpOptionCode code, byte[] value)
+ {
+ if (value is null)
+ throw new ArgumentNullException(nameof(value));
+
+ _code = code;
+ _value = value;
+ }
+
protected DhcpOption(DhcpOptionCode code, Stream s)
{
_code = code;
@@ -223,6 +247,9 @@ namespace DnsServerCore.Dhcp
case DhcpOptionCode.CAPWAPAccessControllerAddresses:
return new CAPWAPAccessControllerOption(s);
+ case DhcpOptionCode.TftpServerAddress:
+ return new TftpServerAddressOption(s);
+
case DhcpOptionCode.Pad:
case DhcpOptionCode.End:
return new DhcpOption(optionCode);
@@ -233,6 +260,38 @@ namespace DnsServerCore.Dhcp
}
}
+ protected static byte[] ParseColonHexString(string value)
+ {
+ int i;
+ int j = -1;
+ string strHex;
+ int b;
+
+ using (MemoryStream mS = new MemoryStream())
+ {
+ while (true)
+ {
+ i = value.IndexOf(':', j + 1);
+ if (i < 0)
+ i = value.Length;
+
+ strHex = value.Substring(j + 1, i - j - 1);
+
+ if (!int.TryParse(strHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out b) || (b < byte.MinValue) || (b > byte.MaxValue))
+ throw new InvalidDataException("VendorSpecificInformation option data must be a colon (:) separated hex string.");
+
+ mS.WriteByte((byte)b);
+
+ if (i == value.Length)
+ break;
+
+ j = i;
+ }
+
+ return mS.ToArray();
+ }
+ }
+
#endregion
#region internal
@@ -321,6 +380,9 @@ namespace DnsServerCore.Dhcp
public DhcpOptionCode Code
{ get { return _code; } }
+ public byte[] RawValue
+ { get { return _value; } }
+
#endregion
}
}
diff --git a/DnsServerCore/Dhcp/DhcpServer.cs b/DnsServerCore/Dhcp/DhcpServer.cs
index b7567527..98639e03 100644
--- a/DnsServerCore/Dhcp/DhcpServer.cs
+++ b/DnsServerCore/Dhcp/DhcpServer.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -130,11 +130,9 @@ namespace DnsServerCore.Dhcp
{
Stop();
- if (_saveModifiedDnsAuthZonesTimer is not null)
- _saveModifiedDnsAuthZonesTimer.Dispose();
+ _saveModifiedDnsAuthZonesTimer?.Dispose();
- if (_maintenanceTimer is not null)
- _maintenanceTimer.Dispose();
+ _maintenanceTimer?.Dispose();
if (_scopes is not null)
{
@@ -204,9 +202,7 @@ namespace DnsServerCore.Dhcp
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log != null)
- log.Write(result.RemoteEndPoint as IPEndPoint, ex);
+ _log?.Write(result.RemoteEndPoint as IPEndPoint, ex);
}
}
}
@@ -224,9 +220,7 @@ namespace DnsServerCore.Dhcp
break; //server stopping
default:
- LogManager log = _log;
- if (log != null)
- log.Write(ex);
+ _log?.Write(ex);
throw;
}
@@ -236,9 +230,7 @@ namespace DnsServerCore.Dhcp
if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
return; //server stopping
- LogManager log = _log;
- if (log != null)
- log.Write(ex);
+ _log?.Write(ex);
throw;
}
@@ -292,9 +284,7 @@ namespace DnsServerCore.Dhcp
if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
return; //server stopping
- LogManager log = _log;
- if (log != null)
- log.Write(remoteEP, ex);
+ _log?.Write(remoteEP, ex);
}
}
@@ -343,9 +333,7 @@ namespace DnsServerCore.Dhcp
return null;
//log ip offer
- LogManager log = _log;
- if (log != null)
- log.Write(remoteEP, "DHCP Server offered IP address [" + offer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
+ _log?.Write(remoteEP, "DHCP Server offered IP address [" + offer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
return DhcpMessage.CreateReply(request, offer.Address, scope.ServerAddress ?? serverIdentifierAddress, scope.ServerHostName, scope.BootFileName, options);
}
@@ -481,9 +469,7 @@ namespace DnsServerCore.Dhcp
scope.CommitLease(leaseOffer);
//log ip lease
- LogManager log = _log;
- if (log != null)
- log.Write(remoteEP, "DHCP Server leased IP address [" + leaseOffer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
+ _log?.Write(remoteEP, "DHCP Server leased IP address [" + leaseOffer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
if (string.IsNullOrWhiteSpace(scope.DomainName))
{
@@ -553,9 +539,7 @@ namespace DnsServerCore.Dhcp
scope.ReleaseLease(lease);
//log issue
- LogManager log = _log;
- if (log != null)
- log.Write(remoteEP, "DHCP Server received DECLINE message for scope '" + scope.Name + "': " + lease.GetClientInfo() + " detected that IP address [" + lease.Address + "] is already in use.");
+ _log?.Write(remoteEP, "DHCP Server received DECLINE message for scope '" + scope.Name + "': " + lease.GetClientInfo() + " detected that IP address [" + lease.Address + "] is already in use.");
//update dns
UpdateDnsAuthZone(false, scope, lease);
@@ -591,9 +575,7 @@ namespace DnsServerCore.Dhcp
scope.ReleaseLease(lease);
//log ip lease release
- LogManager log = _log;
- if (log != null)
- log.Write(remoteEP, "DHCP Server released IP address [" + lease.Address.ToString() + "] that was leased to " + lease.GetClientInfo() + " for scope: " + scope.Name);
+ _log?.Write(remoteEP, "DHCP Server released IP address [" + lease.Address.ToString() + "] that was leased to " + lease.GetClientInfo() + " for scope: " + scope.Name);
//update dns
UpdateDnsAuthZone(false, scope, lease);
@@ -613,9 +595,7 @@ namespace DnsServerCore.Dhcp
IPAddress serverIdentifierAddress = scope.InterfaceAddress.Equals(IPAddress.Any) ? ipPacketInformation.Address : scope.InterfaceAddress;
//log inform
- LogManager log = _log;
- if (log != null)
- log.Write(remoteEP, "DHCP Server received INFORM message from " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
+ _log?.Write(remoteEP, "DHCP Server received INFORM message from " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
List options = await scope.GetOptionsAsync(request, serverIdentifierAddress, null, _dnsServer);
if (options is null)
@@ -752,7 +732,6 @@ namespace DnsServerCore.Dhcp
string zoneName = null;
string reverseDomain = Zone.GetReverseZone(address, 32);
string reverseZoneName = null;
- LogManager log = _log;
if (add)
{
@@ -764,7 +743,7 @@ namespace DnsServerCore.Dhcp
zoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(scope.DomainName, _dnsServer.ServerDomain, false);
if (zoneInfo is null)
{
- log?.Write("DHCP Server failed to create DNS primary zone '" + scope.DomainName + "'.");
+ _log?.Write("DHCP Server failed to create DNS primary zone '" + scope.DomainName + "'.");
return;
}
@@ -774,7 +753,7 @@ namespace DnsServerCore.Dhcp
_authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
_authManager.SaveConfigFile();
- log?.Write("DHCP Server create DNS primary zone '" + zoneInfo.Name + "'.");
+ _log?.Write("DHCP Server create DNS primary zone '" + zoneInfo.Name + "'.");
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
else if ((zoneInfo.Type != AuthZoneType.Primary) && (zoneInfo.Type != AuthZoneType.Forwarder))
@@ -786,7 +765,7 @@ namespace DnsServerCore.Dhcp
zoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(scope.DomainName, _dnsServer.ServerDomain, false);
if (zoneInfo is null)
{
- log?.Write("DHCP Server failed to create DNS primary zone '" + scope.DomainName + "'.");
+ _log?.Write("DHCP Server failed to create DNS primary zone '" + scope.DomainName + "'.");
return;
}
@@ -796,7 +775,7 @@ namespace DnsServerCore.Dhcp
_authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
_authManager.SaveConfigFile();
- log?.Write("DHCP Server create DNS primary zone '" + zoneInfo.Name + "'.");
+ _log?.Write("DHCP Server create DNS primary zone '" + zoneInfo.Name + "'.");
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
@@ -821,7 +800,7 @@ namespace DnsServerCore.Dhcp
}
_dnsServer.AuthZoneManager.SetRecords(zoneName, domain, DnsResourceRecordType.A, scope.DnsTtl, new DnsResourceRecordData[] { new DnsARecordData(address) });
- log?.Write("DHCP Server updated DNS A record '" + domain + "' with IP address [" + address.ToString() + "].");
+ _log?.Write("DHCP Server updated DNS A record '" + domain + "' with IP address [" + address.ToString() + "].");
//update reverse zone
AuthZoneInfo reverseZoneInfo = _dnsServer.AuthZoneManager.FindAuthZoneInfo(reverseDomain);
@@ -833,7 +812,7 @@ namespace DnsServerCore.Dhcp
reverseZoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(reverseZone, _dnsServer.ServerDomain, false);
if (reverseZoneInfo is null)
{
- log?.Write("DHCP Server failed to create DNS primary zone '" + reverseZone + "'.");
+ _log?.Write("DHCP Server failed to create DNS primary zone '" + reverseZone + "'.");
return;
}
@@ -843,7 +822,7 @@ namespace DnsServerCore.Dhcp
_authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
_authManager.SaveConfigFile();
- log?.Write("DHCP Server create DNS primary zone '" + reverseZoneInfo.Name + "'.");
+ _log?.Write("DHCP Server create DNS primary zone '" + reverseZoneInfo.Name + "'.");
_dnsServer.AuthZoneManager.SaveZoneFile(reverseZoneInfo.Name);
}
else if ((reverseZoneInfo.Type != AuthZoneType.Primary) && (reverseZoneInfo.Type != AuthZoneType.Forwarder))
@@ -857,7 +836,7 @@ namespace DnsServerCore.Dhcp
reverseZoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(reverseZone, _dnsServer.ServerDomain, false);
if (reverseZoneInfo is null)
{
- log?.Write("DHCP Server failed to create DNS primary zone '" + reverseZone + "'.");
+ _log?.Write("DHCP Server failed to create DNS primary zone '" + reverseZone + "'.");
return;
}
@@ -867,13 +846,13 @@ namespace DnsServerCore.Dhcp
_authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
_authManager.SaveConfigFile();
- log?.Write("DHCP Server create DNS primary zone '" + reverseZoneInfo.Name + "'.");
+ _log?.Write("DHCP Server create DNS primary zone '" + reverseZoneInfo.Name + "'.");
_dnsServer.AuthZoneManager.SaveZoneFile(reverseZoneInfo.Name);
}
reverseZoneName = reverseZoneInfo.Name;
_dnsServer.AuthZoneManager.SetRecords(reverseZoneName, reverseDomain, DnsResourceRecordType.PTR, scope.DnsTtl, new DnsResourceRecordData[] { new DnsPTRRecordData(domain) });
- log?.Write("DHCP Server updated DNS PTR record '" + reverseDomain + "' with domain name '" + domain + "'.");
+ _log?.Write("DHCP Server updated DNS PTR record '" + reverseDomain + "' with domain name '" + domain + "'.");
}
else
{
@@ -884,7 +863,7 @@ namespace DnsServerCore.Dhcp
//primary zone exists
zoneName = zoneInfo.Name;
_dnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, DnsResourceRecordType.A, new DnsARecordData(address));
- log?.Write("DHCP Server deleted DNS A record '" + domain + "' with address [" + address.ToString() + "].");
+ _log?.Write("DHCP Server deleted DNS A record '" + domain + "' with address [" + address.ToString() + "].");
}
//remove from reverse zone
@@ -894,7 +873,7 @@ namespace DnsServerCore.Dhcp
//primary reverse zone exists
reverseZoneName = reverseZoneInfo.Name;
_dnsServer.AuthZoneManager.DeleteRecord(reverseZoneName, reverseDomain, DnsResourceRecordType.PTR, new DnsPTRRecordData(domain));
- log?.Write("DHCP Server deleted DNS PTR record '" + reverseDomain + "' with domain '" + domain + "'.");
+ _log?.Write("DHCP Server deleted DNS PTR record '" + reverseDomain + "' with domain '" + domain + "'.");
}
}
@@ -908,9 +887,7 @@ namespace DnsServerCore.Dhcp
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log != null)
- log.Write(ex);
+ _log?.Write(ex);
}
}
@@ -936,9 +913,7 @@ namespace DnsServerCore.Dhcp
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log != null)
- log.Write(ex);
+ _log?.Write(ex);
}
}
}
@@ -1076,17 +1051,13 @@ namespace DnsServerCore.Dhcp
UpdateDnsAuthZone(utcNow < lease.Value.LeaseExpires, scope, lease.Value); //lease valid
}
- LogManager log = _log;
- if (log != null)
- log.Write(dhcpEP, "DHCP Server successfully activated scope: " + scope.Name);
+ _log?.Write(dhcpEP, "DHCP Server successfully activated scope: " + scope.Name);
return true;
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log is not null)
- log.Write(dhcpEP, "DHCP Server failed to activate scope: " + scope.Name + "\r\n" + ex.ToString());
+ _log?.Write(dhcpEP, "DHCP Server failed to activate scope: " + scope.Name + "\r\n" + ex.ToString());
if (throwException)
throw;
@@ -1116,17 +1087,13 @@ namespace DnsServerCore.Dhcp
UpdateDnsAuthZone(false, scope, lease.Value);
}
- LogManager log = _log;
- if (log != null)
- log.Write(dhcpEP, "DHCP Server successfully deactivated scope: " + scope.Name);
+ _log?.Write(dhcpEP, "DHCP Server successfully deactivated scope: " + scope.Name);
return true;
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log is not null)
- log.Write(dhcpEP, "DHCP Server failed to deactivate scope: " + scope.Name + "\r\n" + ex.ToString());
+ _log?.Write(dhcpEP, "DHCP Server failed to deactivate scope: " + scope.Name + "\r\n" + ex.ToString());
if (throwException)
throw;
@@ -1154,9 +1121,7 @@ namespace DnsServerCore.Dhcp
scope.SetEnabled(false);
}
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server successfully loaded scope: " + scope.Name);
+ _log?.Write("DHCP Server successfully loaded scope: " + scope.Name);
}
private void UnloadScope(Scope scope)
@@ -1168,9 +1133,7 @@ namespace DnsServerCore.Dhcp
{
removedScope.Dispose();
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server successfully unloaded scope: " + scope.Name);
+ _log?.Write("DHCP Server successfully unloaded scope: " + scope.Name);
}
}
@@ -1194,15 +1157,11 @@ namespace DnsServerCore.Dhcp
await LoadScopeAsync(new Scope(new BinaryReader(fS), _log), true);
}
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server successfully loaded scope file: " + scopeFile);
+ _log?.Write("DHCP Server successfully loaded scope file: " + scopeFile);
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server failed to load scope file: " + scopeFile + "\r\n" + ex.ToString());
+ _log?.Write("DHCP Server failed to load scope file: " + scopeFile + "\r\n" + ex.ToString());
}
}
@@ -1217,15 +1176,11 @@ namespace DnsServerCore.Dhcp
scope.WriteTo(new BinaryWriter(fS));
}
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server successfully saved scope file: " + scopeFile);
+ _log?.Write("DHCP Server successfully saved scope file: " + scopeFile);
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server failed to save scope file: " + scopeFile + "\r\n" + ex.ToString());
+ _log?.Write("DHCP Server failed to save scope file: " + scopeFile + "\r\n" + ex.ToString());
}
}
@@ -1237,15 +1192,11 @@ namespace DnsServerCore.Dhcp
{
File.Delete(scopeFile);
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server successfully deleted scope file: " + scopeFile);
+ _log?.Write("DHCP Server successfully deleted scope file: " + scopeFile);
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server failed to delete scope file: " + scopeFile + "\r\n" + ex.ToString());
+ _log?.Write("DHCP Server failed to delete scope file: " + scopeFile + "\r\n" + ex.ToString());
}
}
@@ -1277,9 +1228,7 @@ namespace DnsServerCore.Dhcp
List expiredLeases = scope.Value.RemoveExpiredLeases();
if (expiredLeases.Count > 0)
{
- LogManager log = _log;
- if (log != null)
- log.Write("DHCP Server removed " + expiredLeases.Count + " lease(s) from scope: " + scope.Value.Name);
+ _log?.Write("DHCP Server removed " + expiredLeases.Count + " lease(s) from scope: " + scope.Value.Name);
foreach (Lease expiredLease in expiredLeases)
UpdateDnsAuthZone(false, scope.Value, expiredLease);
@@ -1290,9 +1239,7 @@ namespace DnsServerCore.Dhcp
}
catch (Exception ex)
{
- LogManager log = _log;
- if (log != null)
- log.Write(ex);
+ _log?.Write(ex);
}
finally
{
diff --git a/DnsServerCore/Dhcp/Lease.cs b/DnsServerCore/Dhcp/Lease.cs
index 0635844e..89737bfc 100644
--- a/DnsServerCore/Dhcp/Lease.cs
+++ b/DnsServerCore/Dhcp/Lease.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -88,7 +88,7 @@ namespace DnsServerCore.Dhcp
_hostName = null;
_hardwareAddress = bR.ReadBuffer();
- _address = IPAddressExtension.ReadFrom(bR);
+ _address = IPAddressExtensions.ReadFrom(bR);
if (version >= 2)
{
diff --git a/DnsServerCore/Dhcp/Options/ClasslessStaticRouteOption.cs b/DnsServerCore/Dhcp/Options/ClasslessStaticRouteOption.cs
index e59fe1cf..50dc25b5 100644
--- a/DnsServerCore/Dhcp/Options/ClasslessStaticRouteOption.cs
+++ b/DnsServerCore/Dhcp/Options/ClasslessStaticRouteOption.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -109,7 +109,7 @@ namespace DnsServerCore.Dhcp.Options
s.ReadBytes(destinationBuffer, 0, Convert.ToInt32(Math.Ceiling(Convert.ToDecimal(subnetMaskWidth) / 8)));
_destination = new IPAddress(destinationBuffer);
- _subnetMask = IPAddressExtension.GetSubnetMask(subnetMaskWidth);
+ _subnetMask = IPAddressExtensions.GetSubnetMask(subnetMaskWidth);
_router = new IPAddress(s.ReadBytes(4));
}
diff --git a/DnsServerCore/Dhcp/Options/TftpServerAddressOption.cs b/DnsServerCore/Dhcp/Options/TftpServerAddressOption.cs
new file mode 100644
index 00000000..67588f1f
--- /dev/null
+++ b/DnsServerCore/Dhcp/Options/TftpServerAddressOption.cs
@@ -0,0 +1,79 @@
+/*
+Technitium DNS Server
+Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using TechnitiumLibrary.IO;
+
+namespace DnsServerCore.Dhcp.Options
+{
+ class TftpServerAddressOption : DhcpOption
+ {
+ #region variables
+
+ IReadOnlyCollection _addresses;
+
+ #endregion
+
+ #region constructor
+
+ public TftpServerAddressOption(IReadOnlyCollection addresses)
+ : base(DhcpOptionCode.TftpServerAddress)
+ {
+ _addresses = addresses;
+ }
+
+ public TftpServerAddressOption(Stream s)
+ : base(DhcpOptionCode.TftpServerAddress, s)
+ { }
+
+ #endregion
+
+ #region protected
+
+ protected override void ParseOptionValue(Stream s)
+ {
+ if ((s.Length % 4 != 0) || (s.Length < 4))
+ throw new InvalidDataException();
+
+ IPAddress[] addresses = new IPAddress[s.Length / 4];
+
+ for (int i = 0; i < addresses.Length; i++)
+ addresses[i] = new IPAddress(s.ReadBytes(4));
+
+ _addresses = addresses;
+ }
+
+ protected override void WriteOptionValue(Stream s)
+ {
+ foreach (IPAddress address in _addresses)
+ s.Write(address.GetAddressBytes());
+ }
+
+ #endregion
+
+ #region properties
+
+ public IReadOnlyCollection Addresses
+ { get { return _addresses; } }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/Dhcp/Options/VendorSpecificInformationOption.cs b/DnsServerCore/Dhcp/Options/VendorSpecificInformationOption.cs
index a9de2681..cb7c83da 100644
--- a/DnsServerCore/Dhcp/Options/VendorSpecificInformationOption.cs
+++ b/DnsServerCore/Dhcp/Options/VendorSpecificInformationOption.cs
@@ -18,7 +18,6 @@ along with this program. If not, see .
*/
using System;
-using System.Globalization;
using System.IO;
using TechnitiumLibrary.IO;
@@ -37,7 +36,10 @@ namespace DnsServerCore.Dhcp.Options
public VendorSpecificInformationOption(string hexInfo)
: base(DhcpOptionCode.VendorSpecificInformation)
{
- _information = ParseHexString(hexInfo);
+ if (hexInfo.Contains(':'))
+ _information = ParseColonHexString(hexInfo);
+ else
+ _information = Convert.FromHexString(hexInfo);
}
public VendorSpecificInformationOption(byte[] information)
@@ -52,42 +54,6 @@ namespace DnsServerCore.Dhcp.Options
#endregion
- #region private
-
- private static byte[] ParseHexString(string value)
- {
- int i;
- int j = -1;
- string strHex;
- int b;
-
- using (MemoryStream mS = new MemoryStream())
- {
- while (true)
- {
- i = value.IndexOf(':', j + 1);
- if (i < 0)
- i = value.Length;
-
- strHex = value.Substring(j + 1, i - j - 1);
-
- if (!int.TryParse(strHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out b) || (b < byte.MinValue) || (b > byte.MaxValue))
- throw new InvalidDataException("VendorSpecificInformation option data must be a colon (:) separated hex string.");
-
- mS.WriteByte((byte)b);
-
- if (i == value.Length)
- break;
-
- j = i;
- }
-
- return mS.ToArray();
- }
- }
-
- #endregion
-
#region protected
protected override void ParseOptionValue(Stream s)
@@ -102,15 +68,6 @@ namespace DnsServerCore.Dhcp.Options
#endregion
- #region public
-
- public override string ToString()
- {
- return BitConverter.ToString(_information).Replace("-", ":");
- }
-
- #endregion
-
#region properties
public byte[] Information
diff --git a/DnsServerCore/Dhcp/Scope.cs b/DnsServerCore/Dhcp/Scope.cs
index a2cd9afb..b33db552 100644
--- a/DnsServerCore/Dhcp/Scope.cs
+++ b/DnsServerCore/Dhcp/Scope.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -76,8 +76,10 @@ namespace DnsServerCore.Dhcp
IReadOnlyCollection _staticRoutes;
IReadOnlyDictionary _vendorInfo;
IReadOnlyCollection _capwapAcIpAddresses;
+ IReadOnlyCollection _tftpServerAddreses;
//advanced options
+ IReadOnlyCollection _genericOptions;
IReadOnlyCollection _exclusions;
readonly ConcurrentDictionary _reservedLeases = new ConcurrentDictionary();
bool _allowOnlyReservedLeases;
@@ -130,10 +132,11 @@ namespace DnsServerCore.Dhcp
case 5:
case 6:
case 7:
+ case 8:
_name = bR.ReadShortString();
_enabled = bR.ReadBoolean();
- ChangeNetwork(IPAddressExtension.ReadFrom(bR), IPAddressExtension.ReadFrom(bR), IPAddressExtension.ReadFrom(bR));
+ ChangeNetwork(IPAddressExtensions.ReadFrom(bR), IPAddressExtensions.ReadFrom(bR), IPAddressExtensions.ReadFrom(bR));
_leaseTimeDays = bR.ReadUInt16();
_leaseTimeHours = bR.ReadByte();
@@ -172,7 +175,7 @@ namespace DnsServerCore.Dhcp
if (version >= 2)
{
- _serverAddress = IPAddressExtension.ReadFrom(bR);
+ _serverAddress = IPAddressExtensions.ReadFrom(bR);
if (_serverAddress.Equals(IPAddress.Any))
_serverAddress = null;
}
@@ -188,7 +191,7 @@ namespace DnsServerCore.Dhcp
_bootFileName = null;
}
- _routerAddress = IPAddressExtension.ReadFrom(bR);
+ _routerAddress = IPAddressExtensions.ReadFrom(bR);
if (_routerAddress.Equals(IPAddress.Any))
_routerAddress = null;
@@ -206,7 +209,7 @@ namespace DnsServerCore.Dhcp
IPAddress[] dnsServers = new IPAddress[count];
for (int i = 0; i < count; i++)
- dnsServers[i] = IPAddressExtension.ReadFrom(bR);
+ dnsServers[i] = IPAddressExtensions.ReadFrom(bR);
_dnsServers = dnsServers;
}
@@ -220,7 +223,7 @@ namespace DnsServerCore.Dhcp
IPAddress[] winsServers = new IPAddress[count];
for (int i = 0; i < count; i++)
- winsServers[i] = IPAddressExtension.ReadFrom(bR);
+ winsServers[i] = IPAddressExtensions.ReadFrom(bR);
_winsServers = winsServers;
}
@@ -233,7 +236,7 @@ namespace DnsServerCore.Dhcp
IPAddress[] ntpServers = new IPAddress[count];
for (int i = 0; i < count; i++)
- ntpServers[i] = IPAddressExtension.ReadFrom(bR);
+ ntpServers[i] = IPAddressExtensions.ReadFrom(bR);
_ntpServers = ntpServers;
}
@@ -293,12 +296,46 @@ namespace DnsServerCore.Dhcp
IPAddress[] capwapAcIpAddresses = new IPAddress[count];
for (int i = 0; i < count; i++)
- capwapAcIpAddresses[i] = IPAddressExtension.ReadFrom(bR);
+ capwapAcIpAddresses[i] = IPAddressExtensions.ReadFrom(bR);
_capwapAcIpAddresses = capwapAcIpAddresses;
}
}
+ if (version >= 8)
+ {
+ int count = bR.ReadByte();
+ if (count > 0)
+ {
+ IPAddress[] tftpServerAddreses = new IPAddress[count];
+
+ for (int i = 0; i < count; i++)
+ tftpServerAddreses[i] = IPAddressExtensions.ReadFrom(bR);
+
+ _tftpServerAddreses = tftpServerAddreses;
+ }
+ }
+
+ if (version >= 8)
+ {
+ int count = bR.ReadByte();
+ if (count > 0)
+ {
+ DhcpOption[] genericOptions = new DhcpOption[count];
+
+ for (int i = 0; i < count; i++)
+ {
+ DhcpOptionCode code = (DhcpOptionCode)bR.ReadByte();
+ short length = bR.ReadInt16();
+ byte[] value = bR.ReadBytes(length);
+
+ genericOptions[i] = new DhcpOption(code, value);
+ }
+
+ _genericOptions = genericOptions;
+ }
+ }
+
{
int count = bR.ReadByte();
if (count > 0)
@@ -306,7 +343,7 @@ namespace DnsServerCore.Dhcp
Exclusion[] exclusions = new Exclusion[count];
for (int i = 0; i < count; i++)
- exclusions[i] = new Exclusion(IPAddressExtension.ReadFrom(bR), IPAddressExtension.ReadFrom(bR));
+ exclusions[i] = new Exclusion(IPAddressExtensions.ReadFrom(bR), IPAddressExtensions.ReadFrom(bR));
_exclusions = exclusions;
}
@@ -955,12 +992,12 @@ namespace DnsServerCore.Dhcp
return null;
}
- offerAddress = IPAddressExtension.ConvertNumberToIp(_startingAddress.ConvertIpToNumber() - 1u);
+ offerAddress = IPAddressExtensions.ConvertNumberToIp(_startingAddress.ConvertIpToNumber() - 1u);
offerAddressWasResetFromEnd = true;
continue;
}
- offerAddress = IPAddressExtension.ConvertNumberToIp(nextOfferAddressNumber);
+ offerAddress = IPAddressExtensions.ConvertNumberToIp(nextOfferAddressNumber);
AddressStatus addressStatus = await IsAddressAvailableAsync(offerAddress);
if (addressStatus.IsAddressAvailable)
@@ -1121,6 +1158,27 @@ namespace DnsServerCore.Dhcp
options.Add(new CAPWAPAccessControllerOption(_capwapAcIpAddresses));
break;
+
+ case DhcpOptionCode.TftpServerAddress:
+ if (_tftpServerAddreses is not null)
+ options.Add(new TftpServerAddressOption(_tftpServerAddreses));
+
+ break;
+
+ default:
+ if (_genericOptions is not null)
+ {
+ foreach (DhcpOption genericOption in _genericOptions)
+ {
+ if (optionCode == genericOption.Code)
+ {
+ options.Add(genericOption);
+ break;
+ }
+ }
+ }
+
+ break;
}
}
}
@@ -1314,13 +1372,13 @@ namespace DnsServerCore.Dhcp
if (broadcastAddressNumber == endingAddressNumber)
throw new ArgumentException("Ending address cannot be same as the broadcast address.");
- _networkAddress = IPAddressExtension.ConvertNumberToIp(networkAddressNumber);
- _broadcastAddress = IPAddressExtension.ConvertNumberToIp(broadcastAddressNumber);
+ _networkAddress = IPAddressExtensions.ConvertNumberToIp(networkAddressNumber);
+ _broadcastAddress = IPAddressExtensions.ConvertNumberToIp(broadcastAddressNumber);
_lastAddressOfferedLock.Wait();
try
{
- _lastAddressOffered = IPAddressExtension.ConvertNumberToIp(startingAddressNumber - 1u);
+ _lastAddressOffered = IPAddressExtensions.ConvertNumberToIp(startingAddressNumber - 1u);
}
finally
{
@@ -1429,7 +1487,7 @@ namespace DnsServerCore.Dhcp
public void WriteTo(BinaryWriter bW)
{
bW.Write(Encoding.ASCII.GetBytes("SC"));
- bW.Write((byte)7); //version
+ bW.Write((byte)8); //version
bW.WriteShortString(_name);
bW.Write(_enabled);
@@ -1576,6 +1634,34 @@ namespace DnsServerCore.Dhcp
capwapAcIpAddress.WriteTo(bW);
}
+ if (_tftpServerAddreses is null)
+ {
+ bW.Write((byte)0);
+ }
+ else
+ {
+ bW.Write(Convert.ToByte(_tftpServerAddreses.Count));
+
+ foreach (IPAddress tftpServerAddress in _tftpServerAddreses)
+ tftpServerAddress.WriteTo(bW);
+ }
+
+ if (_genericOptions is null)
+ {
+ bW.Write((byte)0);
+ }
+ else
+ {
+ bW.Write(Convert.ToByte(_genericOptions.Count));
+
+ foreach (DhcpOption genericOption in _genericOptions)
+ {
+ bW.Write((byte)genericOption.Code);
+ bW.Write(Convert.ToInt16(genericOption.RawValue.Length));
+ bW.Write(genericOption.RawValue);
+ }
+ }
+
if (_exclusions is null)
{
bW.Write((byte)0);
@@ -1898,6 +1984,22 @@ namespace DnsServerCore.Dhcp
}
}
+ public IReadOnlyCollection TftpServerAddresses
+ {
+ get { return _tftpServerAddreses; }
+ set
+ {
+ ValidateIpv4(value, nameof(TftpServerAddresses));
+ _tftpServerAddreses = value;
+ }
+ }
+
+ public IReadOnlyCollection GenericOptions
+ {
+ get { return _genericOptions; }
+ set { _genericOptions = value; }
+ }
+
public IReadOnlyCollection Exclusions
{
get { return _exclusions; }
diff --git a/DnsServerCore/Dns/Applications/DnsServerInternal.cs b/DnsServerCore/Dns/Applications/DnsServerInternal.cs
index e00783a8..836f3f66 100644
--- a/DnsServerCore/Dns/Applications/DnsServerInternal.cs
+++ b/DnsServerCore/Dns/Applications/DnsServerInternal.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -53,6 +53,11 @@ namespace DnsServerCore.Dns.Applications
return _dnsServer.DirectQueryAsync(question, timeout, true);
}
+ public Task DirectQueryAsync(DnsDatagram request, int timeout = 4000)
+ {
+ return _dnsServer.DirectQueryAsync(request, timeout, true);
+ }
+
public void WriteLog(string message)
{
LogManager log = _dnsServer.LogManager;
diff --git a/DnsServerCore/Dns/DnsServer.cs b/DnsServerCore/Dns/DnsServer.cs
index ef59a9af..d71f67f9 100644
--- a/DnsServerCore/Dns/DnsServer.cs
+++ b/DnsServerCore/Dns/DnsServer.cs
@@ -1,6 +1,6 @@
/*
Technitium DNS Server
-Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -23,29 +23,40 @@ using DnsServerCore.Dns.ResourceRecords;
using DnsServerCore.Dns.Trees;
using DnsServerCore.Dns.ZoneManagers;
using DnsServerCore.Dns.Zones;
-using Newtonsoft.Json;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.AspNetCore.Server.Kestrel.Https;
+using Microsoft.AspNetCore.StaticFiles;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
+using System.Net.Quic;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.ExceptionServices;
using System.Security.Cryptography.X509Certificates;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TechnitiumLibrary;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns;
+using TechnitiumLibrary.Net.Dns.ClientConnection;
using TechnitiumLibrary.Net.Dns.EDnsOptions;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
-using TechnitiumLibrary.Net.Http;
using TechnitiumLibrary.Net.Proxy;
namespace DnsServerCore.Dns
{
+#pragma warning disable CA2252 // This API requires opting into preview features
+#pragma warning disable CA1416 // Validate platform compatibility
+
public enum DnsServerRecursion : byte
{
Deny = 0,
@@ -61,7 +72,7 @@ namespace DnsServerCore.Dns
CustomAddress = 2
}
- public sealed class DnsServer : IDisposable, IDnsClient
+ public sealed class DnsServer : IAsyncDisposable, IDisposable, IDnsClient
{
#region enum
@@ -82,6 +93,8 @@ namespace DnsServerCore.Dns
const int SERVE_STALE_WAIT_TIME = 1800;
static readonly IPEndPoint IPENDPOINT_ANY_0 = new IPEndPoint(IPAddress.Any, 0);
+ static readonly IReadOnlyCollection _aRecords = new DnsARecordData[] { new DnsARecordData(IPAddress.Any) };
+ static readonly IReadOnlyCollection _aaaaRecords = new DnsAAAARecordData[] { new DnsAAAARecordData(IPAddress.IPv6Any) };
string _serverDomain;
readonly string _configFolder;
@@ -93,16 +106,10 @@ namespace DnsServerCore.Dns
readonly List _udpListeners = new List();
readonly List _tcpListeners = new List();
- readonly List _httpListeners = new List();
readonly List _tlsListeners = new List();
- readonly List _httpsListeners = new List();
+ readonly List _quicListeners = new List();
- bool _enableDnsOverHttp;
- bool _enableDnsOverTls;
- bool _enableDnsOverHttps;
- bool _isDnsOverHttpsEnabled;
- X509Certificate2 _certificate;
- IReadOnlyDictionary _tsigKeys;
+ WebApplication _dohWebService;
readonly AuthZoneManager _authZoneManager;
readonly AllowedZoneManager _allowedZoneManager;
@@ -112,51 +119,72 @@ namespace DnsServerCore.Dns
readonly DnsApplicationManager _dnsApplicationManager;
readonly ResolverDnsCache _dnsCache;
+ readonly StatsManager _stats;
- readonly IReadOnlyCollection _aRecords = new DnsARecordData[] { new DnsARecordData(IPAddress.Any) };
- readonly IReadOnlyCollection