mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2026-01-04 07:46:26 +00:00
Merge branch 'develop'
This commit is contained in:
344
APIDOCS.md
344
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.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>4.0.1</Version>
|
||||
<Version>5.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -16,10 +16,6 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
@@ -27,6 +23,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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, string>(networkAddress, jsonGroup.GetString());
|
||||
});
|
||||
|
||||
{
|
||||
Dictionary<NetworkAddress, string> networkGroupMap = new Dictionary<NetworkAddress, string>();
|
||||
|
||||
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<string, Group> groups = new Dictionary<string, Group>();
|
||||
|
||||
Dictionary<Uri, BlockList> allAllowListZones = new Dictionary<Uri, BlockList>(0);
|
||||
Dictionary<Uri, BlockList> allBlockListZones = new Dictionary<Uri, BlockList>(0);
|
||||
|
||||
@@ -283,11 +277,9 @@ namespace AdvancedBlocking
|
||||
|
||||
Dictionary<Uri, AdBlockList> allAdBlockListZones = new Dictionary<Uri, AdBlockList>(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<string, Group>(group.Name, group);
|
||||
});
|
||||
|
||||
_allAllowListZones = allAllowListZones;
|
||||
_allBlockListZones = allBlockListZones;
|
||||
@@ -427,10 +419,10 @@ namespace AdvancedBlocking
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
|
||||
public async Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
|
||||
{
|
||||
if (!_enableBlocking)
|
||||
return Task.FromResult<DnsDatagram>(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<DnsDatagram>(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<DnsDatagram>(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<DnsResourceRecord> answer = null;
|
||||
IReadOnlyList<DnsResourceRecord> 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<DnsARecordData> aRecords = new List<DnsARecordData>();
|
||||
List<DnsAAAARecordData> aaaaRecords = new List<DnsAAAARecordData>();
|
||||
|
||||
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<string, object> ReadJsonDomainArray(dynamic jsonDomainArray)
|
||||
private static Tuple<string, object> GetMapEntry(JsonElement jsonElement)
|
||||
{
|
||||
Dictionary<string, object> domains = new Dictionary<string, object>(jsonDomainArray.Count);
|
||||
|
||||
foreach (dynamic jsonDomain in jsonDomainArray)
|
||||
domains.TryAdd(jsonDomain.Value, null);
|
||||
|
||||
return domains;
|
||||
return new Tuple<string, object>(jsonElement.GetString(), null);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Regex> ReadJsonRegexArray(dynamic jsonRegexArray)
|
||||
private static Uri GetUriEntry(string uriString)
|
||||
{
|
||||
List<Regex> regices = new List<Regex>(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<Uri> ReadJsonUrlArray(dynamic jsonUrlArray)
|
||||
private static Regex GetRegexEntry(string pattern)
|
||||
{
|
||||
List<Uri> urls = new List<Uri>(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<string, object> _listZone = new Dictionary<string, object>(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
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
"::"
|
||||
|
||||
45
Apps/AdvancedForwardingApp/AdvancedForwardingApp.csproj
Normal file
45
Apps/AdvancedForwardingApp/AdvancedForwardingApp.csproj
Normal file
@@ -0,0 +1,45 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>1.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
<AssemblyName>AdvancedForwardingApp</AssemblyName>
|
||||
<RootNamespace>AdvancedForwarding</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="adguard-upstreams.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="dnsApp.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
808
Apps/AdvancedForwardingApp/App.cs
Normal file
808
Apps/AdvancedForwardingApp/App.cs
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
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<string, ConfigProxyServer> _configProxyServers;
|
||||
Dictionary<string, ConfigForwarder> _configForwarders;
|
||||
IReadOnlyDictionary<NetworkAddress, string> _networkGroupMap;
|
||||
IReadOnlyDictionary<string, Group> _groups;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_groups is not null)
|
||||
{
|
||||
foreach (KeyValuePair<string, Group> group in _groups)
|
||||
group.Value.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
|
||||
private static IReadOnlyList<DnsForwarderRecordData> GetUpdatedForwarderRecords(IReadOnlyList<DnsForwarderRecordData> forwarderRecords, bool dnssecValidation, ConfigProxyServer configProxyServer)
|
||||
{
|
||||
List<DnsForwarderRecordData> newForwarderRecords = new List<DnsForwarderRecordData>(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<string, Group> 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<string, Group>(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<string, ConfigProxyServer>(proxyServer.Name, proxyServer);
|
||||
}, out Dictionary<string, ConfigProxyServer> configProxyServers))
|
||||
_configProxyServers = configProxyServers;
|
||||
else
|
||||
_configProxyServers = null;
|
||||
|
||||
if (jsonConfig.TryReadArrayAsMap("forwarders", delegate (JsonElement jsonForwarder)
|
||||
{
|
||||
ConfigForwarder forwarder = new ConfigForwarder(jsonForwarder, _configProxyServers);
|
||||
return new Tuple<string, ConfigForwarder>(forwarder.Name, forwarder);
|
||||
}, out Dictionary<string, ConfigForwarder> 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, string>(networkAddress, jsonGroup.GetString());
|
||||
});
|
||||
|
||||
if (jsonConfig.TryReadArrayAsMap("groups", ReadGroup, out Dictionary<string, Group> groups))
|
||||
{
|
||||
if (_groups is not null)
|
||||
{
|
||||
foreach (KeyValuePair<string, Group> 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<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
|
||||
{
|
||||
if (!_enableForwarding)
|
||||
return Task.FromResult<DnsDatagram>(null);
|
||||
|
||||
IPAddress remoteIP = remoteEP.Address;
|
||||
NetworkAddress network = null;
|
||||
string groupName = null;
|
||||
|
||||
foreach (KeyValuePair<NetworkAddress, string> 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<DnsDatagram>(null);
|
||||
|
||||
DnsQuestionRecord question = request.Question[0];
|
||||
string qname = question.Name;
|
||||
|
||||
if (!group.TryGetForwarderRecords(qname, out IReadOnlyList<DnsForwarderRecordData> forwarderRecords))
|
||||
return Task.FromResult<DnsDatagram>(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<string, ConfigProxyServer> _configProxyServers;
|
||||
Dictionary<string, ConfigForwarder> _configForwarders;
|
||||
|
||||
readonly string _name;
|
||||
bool _enableForwarding;
|
||||
IReadOnlyList<Forwarding> _forwardings;
|
||||
IReadOnlyDictionary<string, AdGuardUpstream> _adguardUpstreams;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public Group(IDnsServer dnsServer, Dictionary<string, ConfigProxyServer> configProxyServers, Dictionary<string, ConfigForwarder> 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<string, AdGuardUpstream> adguardUpstream in _adguardUpstreams)
|
||||
adguardUpstream.Value.Dispose();
|
||||
|
||||
_adguardUpstreams = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
|
||||
private Tuple<string, AdGuardUpstream> 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<string, AdGuardUpstream>(adGuardUpstream.Name, adGuardUpstream);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public
|
||||
|
||||
public void ReloadConfig(Dictionary<string, ConfigProxyServer> configProxyServers, Dictionary<string, ConfigForwarder> 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<string, AdGuardUpstream> adguardUpstreams))
|
||||
{
|
||||
if (_adguardUpstreams is not null)
|
||||
{
|
||||
foreach (KeyValuePair<string, AdGuardUpstream> adguardUpstream in _adguardUpstreams)
|
||||
{
|
||||
if (!adguardUpstreams.ContainsKey(adguardUpstream.Key))
|
||||
adguardUpstream.Value.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_adguardUpstreams = adguardUpstreams;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_adguardUpstreams is not null)
|
||||
{
|
||||
foreach (KeyValuePair<string, AdGuardUpstream> adguardUpstream in _adguardUpstreams)
|
||||
adguardUpstream.Value.Dispose();
|
||||
}
|
||||
|
||||
_adguardUpstreams = null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetForwarderRecords(string domain, out IReadOnlyList<DnsForwarderRecordData> 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<string, AdGuardUpstream> 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<DnsForwarderRecordData> _forwarderRecords;
|
||||
readonly IReadOnlyDictionary<string, object> _domainMap;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public Forwarding(JsonElement jsonForwarding, Dictionary<string, ConfigForwarder> configForwarders)
|
||||
{
|
||||
JsonElement jsonForwarders = jsonForwarding.GetProperty("forwarders");
|
||||
List<DnsForwarderRecordData> forwarderRecords = new List<DnsForwarderRecordData>();
|
||||
|
||||
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<string, object>(jsonDomain.GetString().ToLower(), null);
|
||||
});
|
||||
}
|
||||
|
||||
public Forwarding(IReadOnlyList<string> domains, NameServerAddress forwarder, bool dnssecValidation, ConfigProxyServer proxy)
|
||||
: this(new DnsForwarderRecordData[] { GetForwarderRecord(forwarder, dnssecValidation, proxy) }, domains)
|
||||
{ }
|
||||
|
||||
public Forwarding(IReadOnlyList<DnsForwarderRecordData> forwarderRecords, IReadOnlyList<string> domains)
|
||||
{
|
||||
_forwarderRecords = forwarderRecords;
|
||||
|
||||
Dictionary<string, object> domainMap = new Dictionary<string, object>(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<Forwarding> forwardings, out IReadOnlyList<DnsForwarderRecordData> forwarderRecords)
|
||||
{
|
||||
if (forwardings.Count == 1)
|
||||
{
|
||||
if (forwardings[0].TryGetForwarderRecords(domain, out forwarderRecords, out _))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, List<DnsForwarderRecordData>> fwdMap = new Dictionary<string, List<DnsForwarderRecordData>>(forwardings.Count);
|
||||
|
||||
foreach (Forwarding forwarding in forwardings)
|
||||
{
|
||||
if (forwarding.TryGetForwarderRecords(domain, out IReadOnlyList<DnsForwarderRecordData> fwdRecords, out string matchedDomain))
|
||||
{
|
||||
if (fwdMap.TryGetValue(matchedDomain, out List<DnsForwarderRecordData> fwdRecordsList))
|
||||
{
|
||||
fwdRecordsList.AddRange(fwdRecords);
|
||||
}
|
||||
else
|
||||
{
|
||||
fwdRecordsList = new List<DnsForwarderRecordData>(fwdRecords);
|
||||
fwdMap.Add(matchedDomain, fwdRecordsList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fwdMap.Count > 0)
|
||||
{
|
||||
forwarderRecords = null;
|
||||
string lastMatchedDomain = null;
|
||||
|
||||
foreach (KeyValuePair<string, List<DnsForwarderRecordData>> 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<DnsForwarderRecordData> 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<DnsForwarderRecordData> _defaultForwarderRecords;
|
||||
IReadOnlyList<Forwarding> _forwardings;
|
||||
|
||||
readonly string _configFile;
|
||||
DateTime _configFileLastModified;
|
||||
|
||||
Timer _autoReloadTimer;
|
||||
const int AUTO_RELOAD_TIMER_INTERVAL = 60000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public AdGuardUpstream(IDnsServer dnsServer, Dictionary<string, ConfigProxyServer> 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<DnsForwarderRecordData> defaultForwarderRecords = new List<DnsForwarderRecordData>();
|
||||
List<Forwarding> forwardings = new List<Forwarding>();
|
||||
|
||||
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<string, ConfigProxyServer> 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<DnsForwarderRecordData> 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<DnsForwarderRecordData> _forwarderRecords;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public ConfigForwarder(JsonElement jsonForwarder, Dictionary<string, ConfigProxyServer> 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<DnsForwarderRecordData> ForwarderRecords
|
||||
{ get { return _forwarderRecords; } }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Apps/AdvancedForwardingApp/adguard-upstreams.txt
Normal file
10
Apps/AdvancedForwardingApp/adguard-upstreams.txt
Normal file
@@ -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)
|
||||
77
Apps/AdvancedForwardingApp/dnsApp.config
Normal file
77
Apps/AdvancedForwardingApp/dnsApp.config
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<IPAddress> webServerLocalAddresses = new List<IPAddress>();
|
||||
_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 = @"<html>
|
||||
<head>
|
||||
@@ -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\"");
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>3.0.1</Version>
|
||||
<Version>4.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -26,10 +26,6 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<NetworkAddress, string> networkGroupMap = new Dictionary<NetworkAddress, string>();
|
||||
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, string>(networkAddress, group.GetString());
|
||||
});
|
||||
|
||||
_groups = jsonConfig.ReadArrayAsMap("groups", delegate (JsonElement jsonGroup)
|
||||
{
|
||||
Dictionary<string, Group> groups = new Dictionary<string, Group>();
|
||||
|
||||
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<string, Group>(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<DnsDatagram>(null);
|
||||
|
||||
IPAddress ipv6Address = IPAddressExtension.ParseReverseDomain(question.Name);
|
||||
IPAddress ipv6Address = IPAddressExtensions.ParseReverseDomain(question.Name);
|
||||
if (ipv6Address.AddressFamily != AddressFamily.InterNetworkV6)
|
||||
return Task.FromResult<DnsDatagram>(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<NetworkAddress, NetworkAddress> dns64PrefixMap = new Dictionary<NetworkAddress, NetworkAddress>();
|
||||
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<NetworkAddress, NetworkAddress>(network, dns64Prefix);
|
||||
});
|
||||
|
||||
_excludedIpv6 = jsonGroup.ReadArray("excludedIpv6", delegate (string strNetworkAddress)
|
||||
{
|
||||
List<NetworkAddress> excludedIpv6 = new List<NetworkAddress>();
|
||||
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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>1.0.1</Version>
|
||||
<Version>2.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -16,10 +16,6 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
@@ -27,6 +23,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
|
||||
813
Apps/DnsBlockListApp/App.cs
Normal file
813
Apps/DnsBlockListApp/App.cs
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
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<string, BlockList> _dnsBlockLists;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_dnsBlockLists is not null)
|
||||
{
|
||||
foreach (KeyValuePair<string, BlockList> 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<string, BlockList> 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<string, BlockList>(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<string, BlockList> dnsBlockLists))
|
||||
{
|
||||
if (_dnsBlockLists is not null)
|
||||
{
|
||||
foreach (KeyValuePair<string, BlockList> dnsBlockList in _dnsBlockLists)
|
||||
{
|
||||
if (!dnsBlockLists.ContainsKey(dnsBlockList.Key))
|
||||
dnsBlockList.Value.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_dnsBlockLists = dnsBlockLists;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_dnsBlockLists is not null)
|
||||
{
|
||||
foreach (KeyValuePair<string, BlockList> dnsBlockList in _dnsBlockLists)
|
||||
dnsBlockList.Value.Dispose();
|
||||
}
|
||||
|
||||
_dnsBlockLists = null;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<DnsDatagram> 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<T>
|
||||
{
|
||||
#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<IPAddress, BlockEntry<IPAddress>> _ipv4Map;
|
||||
Dictionary<IPAddress, BlockEntry<IPAddress>> _ipv6Map;
|
||||
NetworkMap<BlockEntry<NetworkAddress>> _ipv4NetworkMap;
|
||||
NetworkMap<BlockEntry<NetworkAddress>> _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<BlockEntry<IPAddress>> ipv4Addresses = new Queue<BlockEntry<IPAddress>>();
|
||||
Queue<BlockEntry<IPAddress>> ipv6Addresses = new Queue<BlockEntry<IPAddress>>();
|
||||
Queue<BlockEntry<NetworkAddress>> ipv4Networks = new Queue<BlockEntry<NetworkAddress>>();
|
||||
Queue<BlockEntry<NetworkAddress>> ipv6Networks = new Queue<BlockEntry<NetworkAddress>>();
|
||||
|
||||
ipv4Addresses.Enqueue(new BlockEntry<IPAddress>(IPAddress.Parse("127.0.0.2"), "127.0.0.2", "rfc5782 test entry"));
|
||||
ipv6Addresses.Enqueue(new BlockEntry<IPAddress>(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<IPAddress>(networkAddress.Address, responseA, responseTXT));
|
||||
else
|
||||
ipv4Networks.Enqueue(new BlockEntry<NetworkAddress>(networkAddress, responseA, responseTXT));
|
||||
|
||||
break;
|
||||
|
||||
case AddressFamily.InterNetworkV6:
|
||||
if (networkAddress.PrefixLength == 128)
|
||||
ipv6Addresses.Enqueue(new BlockEntry<IPAddress>(networkAddress.Address, responseA, responseTXT));
|
||||
else
|
||||
ipv6Networks.Enqueue(new BlockEntry<NetworkAddress>(networkAddress, responseA, responseTXT));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_blockListFileLastModified = File.GetLastWriteTimeUtc(fS.SafeFileHandle);
|
||||
}
|
||||
|
||||
//load ip lookup list
|
||||
Dictionary<IPAddress, BlockEntry<IPAddress>> ipv4AddressMap = new Dictionary<IPAddress, BlockEntry<IPAddress>>(ipv4Addresses.Count);
|
||||
|
||||
while (ipv4Addresses.Count > 0)
|
||||
{
|
||||
BlockEntry<IPAddress> entry = ipv4Addresses.Dequeue();
|
||||
ipv4AddressMap.TryAdd(entry.Key, entry);
|
||||
}
|
||||
|
||||
Dictionary<IPAddress, BlockEntry<IPAddress>> ipv6AddressMap = new Dictionary<IPAddress, BlockEntry<IPAddress>>(ipv6Addresses.Count);
|
||||
|
||||
while (ipv6Addresses.Count > 0)
|
||||
{
|
||||
BlockEntry<IPAddress> entry = ipv6Addresses.Dequeue();
|
||||
ipv6AddressMap.TryAdd(entry.Key, entry);
|
||||
}
|
||||
|
||||
NetworkMap<BlockEntry<NetworkAddress>> ipv4NetworkMap = new NetworkMap<BlockEntry<NetworkAddress>>(ipv4Networks.Count);
|
||||
|
||||
while (ipv4Networks.Count > 0)
|
||||
{
|
||||
BlockEntry<NetworkAddress> entry = ipv4Networks.Dequeue();
|
||||
ipv4NetworkMap.Add(entry.Key, entry);
|
||||
}
|
||||
|
||||
NetworkMap<BlockEntry<NetworkAddress>> ipv6NetworkMap = new NetworkMap<BlockEntry<NetworkAddress>>(ipv6Networks.Count);
|
||||
|
||||
while (ipv6Networks.Count > 0)
|
||||
{
|
||||
BlockEntry<NetworkAddress> 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<IPAddress> 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<NetworkAddress> 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<IPAddress> 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<NetworkAddress> 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<string, BlockEntry<string>> _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<BlockEntry<string>> domains = new Queue<BlockEntry<string>>();
|
||||
|
||||
domains.Enqueue(new BlockEntry<string>("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<string>(domain.ToLower(), responseA, responseTXT));
|
||||
}
|
||||
|
||||
_blockListFileLastModified = File.GetLastWriteTimeUtc(fS.SafeFileHandle);
|
||||
}
|
||||
|
||||
//load ip lookup list
|
||||
Dictionary<string, BlockEntry<string>> domainMap = new Dictionary<string, BlockEntry<string>>(domains.Count);
|
||||
|
||||
while (domains.Count > 0)
|
||||
{
|
||||
BlockEntry<string> 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<string> 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<string> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Apps/DnsBlockListApp/DnsBlockListApp.csproj
Normal file
48
Apps/DnsBlockListApp/DnsBlockListApp.csproj
Normal file
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>1.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
<AssemblyName>DnsBlockListApp</AssemblyName>
|
||||
<RootNamespace>DnsBlockList</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="dnsApp.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="domain-blocklist.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ip-blocklist.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
20
Apps/DnsBlockListApp/dnsApp.config
Normal file
20
Apps/DnsBlockListApp/dnsApp.config
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
Apps/DnsBlockListApp/domain-blocklist.txt
Normal file
10
Apps/DnsBlockListApp/domain-blocklist.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# DNSBL domain block list
|
||||
# Format: domain A-response TXT-response
|
||||
# Seperator: <space>, <tab>, or <pipe> 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}
|
||||
13
Apps/DnsBlockListApp/ip-blocklist.txt
Normal file
13
Apps/DnsBlockListApp/ip-blocklist.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# DNSBL IP block list
|
||||
# Format: ip/network A-response TXT-response
|
||||
# Seperator: <space>, <tab>, or <pipe> 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
|
||||
@@ -18,12 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<NetworkAddress>();
|
||||
}
|
||||
else
|
||||
{
|
||||
List<NetworkAddress> allowedNetworks = new List<NetworkAddress>();
|
||||
|
||||
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<NetworkAddress>();
|
||||
}
|
||||
else
|
||||
{
|
||||
List<NetworkAddress> blockedNetworks = new List<NetworkAddress>();
|
||||
|
||||
foreach (dynamic blockedNetwork in jsonConfig.blockedNetworks)
|
||||
{
|
||||
blockedNetworks.Add(NetworkAddress.Parse(blockedNetwork.Value));
|
||||
}
|
||||
_allowedNetworks = Array.Empty<NetworkAddress>();
|
||||
|
||||
if (jsonConfig.TryReadArray("blockedNetworks", NetworkAddress.Parse, out NetworkAddress[] blockedNetworks))
|
||||
_blockedNetworks = blockedNetworks;
|
||||
}
|
||||
|
||||
if (jsonConfig.blockedQuestions is null)
|
||||
{
|
||||
_blockedQuestions = Array.Empty<BlockedQuestion>();
|
||||
}
|
||||
else
|
||||
{
|
||||
List<BlockedQuestion> blockedQuestions = new List<BlockedQuestion>();
|
||||
|
||||
foreach (dynamic blockedQuestion in jsonConfig.blockedQuestions)
|
||||
{
|
||||
blockedQuestions.Add(new BlockedQuestion(blockedQuestion));
|
||||
}
|
||||
_blockedNetworks = Array.Empty<NetworkAddress>();
|
||||
|
||||
if (jsonConfig.TryReadArray("blockedQuestions", delegate (JsonElement blockedQuestion) { return new BlockedQuestion(blockedQuestion); }, out BlockedQuestion[] blockedQuestions))
|
||||
_blockedQuestions = blockedQuestions;
|
||||
}
|
||||
else
|
||||
_blockedQuestions = Array.Empty<BlockedQuestion>();
|
||||
|
||||
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;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>3.0.1</Version>
|
||||
<Version>4.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -16,10 +16,6 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
@@ -27,6 +23,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
|
||||
@@ -18,11 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<DnsResourceRecord> answers)
|
||||
private void GetAnswers(JsonElement jsonAddresses, DnsQuestionRecord question, uint appRecordTtl, string healthCheck, Uri healthCheckUrl, List<DnsResourceRecord> 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<DnsResourceRecord> answers)
|
||||
private void GetStatusAnswers(JsonElement jsonAddresses, FailoverType type, DnsQuestionRecord question, uint appRecordTtl, string healthCheck, Uri healthCheckUrl, List<DnsResourceRecord> 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<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(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<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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:
|
||||
|
||||
@@ -18,11 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<DnsResourceRecord> answers;
|
||||
IReadOnlyList<DnsResourceRecord> 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<DnsDatagram>(null);
|
||||
|
||||
List<DnsResourceRecord> txtAnswers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>5.1</Version>
|
||||
<Version>6.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -11,15 +11,11 @@
|
||||
<RootNamespace>Failover</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
|
||||
@@ -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<HealthCheckType>(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<HealthCheckType>(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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DnsDatagram>(null);
|
||||
}
|
||||
|
||||
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(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<DnsDatagram>(null);
|
||||
|
||||
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(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<DnsDatagram>(null);
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DnsDatagram> 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<DnsDatagram>(null);
|
||||
}
|
||||
|
||||
if (jsonContinent is null)
|
||||
return Task.FromResult<DnsDatagram>(null);
|
||||
|
||||
string cname = jsonContinent.Value;
|
||||
string cname = jsonContinent.GetString();
|
||||
if (string.IsNullOrEmpty(cname))
|
||||
return Task.FromResult<DnsDatagram>(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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Version>5.0.1</Version>
|
||||
<Version>6.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -12,14 +12,13 @@
|
||||
<RootNamespace>GeoContinent</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DnsDatagram>(null);
|
||||
}
|
||||
|
||||
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(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<DnsDatagram>(null);
|
||||
|
||||
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(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<DnsDatagram>(null);
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DnsDatagram> 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<DnsDatagram>(null);
|
||||
}
|
||||
|
||||
if (jsonCountry is null)
|
||||
return Task.FromResult<DnsDatagram>(null);
|
||||
|
||||
string cname = jsonCountry.Value;
|
||||
string cname = jsonCountry.GetString();
|
||||
if (string.IsNullOrEmpty(cname))
|
||||
return Task.FromResult<DnsDatagram>(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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Version>5.0.1</Version>
|
||||
<Version>6.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -12,14 +12,13 @@
|
||||
<RootNamespace>GeoCountry</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
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<DnsDatagram>(null);
|
||||
if (jsonClosestServer.ValueKind == JsonValueKind.Undefined)
|
||||
return Task.FromResult<DnsDatagram>(null);
|
||||
|
||||
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(null);
|
||||
if (answers.Count == 0)
|
||||
return Task.FromResult<DnsDatagram>(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<DnsDatagram>(null);
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
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<DnsDatagram>(null);
|
||||
|
||||
dynamic jsonCname = jsonClosestServer.cname;
|
||||
if (jsonCname is null)
|
||||
return Task.FromResult<DnsDatagram>(null);
|
||||
|
||||
string cname = jsonCname.Value;
|
||||
string cname = jsonClosestServer.GetPropertyValue("cname", null);
|
||||
if (string.IsNullOrEmpty(cname))
|
||||
return Task.FromResult<DnsDatagram>(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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Version>5.0.1</Version>
|
||||
<Version>6.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -12,14 +12,13 @@
|
||||
<RootNamespace>GeoDistance</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<DnsResourceRecordType>(jsonBlockedType.Value, true);
|
||||
DnsResourceRecordType blockedType = Enum.Parse<DnsResourceRecordType>(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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>1.0.1</Version>
|
||||
<Version>2.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -16,10 +16,6 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<string, object> ReadJsonDomainArray(dynamic jsonDomainArray)
|
||||
{
|
||||
Dictionary<string, object> domains = new Dictionary<string, object>(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<string, object>(jsonDomainName.GetString(), null); });
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
|
||||
{
|
||||
if (!_enableBlocking)
|
||||
return Task.FromResult<DnsDatagram>(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<DnsResourceRecord> 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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>3.0.1</Version>
|
||||
<Version>4.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -16,10 +16,6 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
@@ -27,6 +23,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DnsLogPage> 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;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Version>3.1</Version>
|
||||
<Version>4.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -18,8 +18,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<NetworkAddress, string> networkGroupMap = new Dictionary<NetworkAddress, string>();
|
||||
|
||||
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<string, Group> groups = new Dictionary<string, Group>();
|
||||
_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, string>(networkAddress, jsonGroupName.GetString());
|
||||
});
|
||||
|
||||
_groups = jsonConfig.ReadArrayAsMap("groups", delegate (JsonElement jsonGroup)
|
||||
{
|
||||
Group group = new Group(jsonGroup);
|
||||
groups.Add(group.Name, group);
|
||||
}
|
||||
return new Tuple<string, Group>(group.Name, group);
|
||||
});
|
||||
|
||||
_groups = groups;
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
while (true);
|
||||
}
|
||||
|
||||
public Task<DnsDatagram> 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<DnsDatagram>(null);
|
||||
|
||||
IPAddress ptrIpAddress = IPAddressExtension.ParseReverseDomain(question.Name);
|
||||
IPAddress ptrIpAddress = IPAddressExtensions.ParseReverseDomain(question.Name);
|
||||
|
||||
if (!group.InternalToExternalTranslation.TryGetValue(ptrIpAddress, out IPAddress externalIp))
|
||||
return Task.FromResult<DnsDatagram>(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<IPAddress, IPAddress> externalToInternalTranslation = new Dictionary<IPAddress, IPAddress>();
|
||||
Dictionary<IPAddress, IPAddress> internalToExternalTranslation = new Dictionary<IPAddress, IPAddress>();
|
||||
|
||||
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<IPAddress, IPAddress> externalToInternalTranslation = new Dictionary<IPAddress, IPAddress>();
|
||||
|
||||
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);
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<string, List<NetworkAddress>>(1);
|
||||
}
|
||||
else
|
||||
if (jsonConfig.TryGetProperty("networks", out JsonElement jsonNetworks))
|
||||
{
|
||||
Dictionary<string, List<NetworkAddress>> networks = new Dictionary<string, List<NetworkAddress>>();
|
||||
|
||||
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<NetworkAddress> networkAddresses = new List<NetworkAddress>();
|
||||
List<NetworkAddress> networkAddresses = new List<NetworkAddress>(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<string, List<NetworkAddress>>(1);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<DnsDatagram> 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<NetworkAddress> 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<NetworkAddress> 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<DnsDatagram>(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!jsonAppRecordData.TryGetProperty("public", out jsonAddresses))
|
||||
return Task.FromResult<DnsDatagram>(null);
|
||||
}
|
||||
}
|
||||
|
||||
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(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<DnsResourceRecord> answers = new List<DnsResourceRecord>();
|
||||
|
||||
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<DnsDatagram>(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<DnsDatagram>(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""
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<DnsDatagram> 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<DnsDatagram>(null);
|
||||
}
|
||||
else
|
||||
jsonCname = jsonAppRecordData.@public;
|
||||
|
||||
if (jsonCname is null)
|
||||
return Task.FromResult<DnsDatagram>(null);
|
||||
{
|
||||
if (!jsonAppRecordData.TryGetProperty("public", out jsonCname))
|
||||
return Task.FromResult<DnsDatagram>(null);
|
||||
}
|
||||
}
|
||||
|
||||
string cname = jsonCname.Value;
|
||||
string cname = jsonCname.GetString();
|
||||
if (string.IsNullOrEmpty(cname))
|
||||
return Task.FromResult<DnsDatagram>(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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Version>5.0.1</Version>
|
||||
<Version>6.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -11,15 +11,11 @@
|
||||
<RootNamespace>SplitHorizon</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
|
||||
@@ -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<DnsDatagram>(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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Version>5.0</Version>
|
||||
<Version>5.0.1</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -12,7 +12,7 @@
|
||||
<RootNamespace>WhatIsMyDns</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -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<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
|
||||
public async Task<DnsDatagram> 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<DnsDatagram>(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<DnsDatagram>(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<DnsDatagram>(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<DnsDatagram>(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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Version>2.0</Version>
|
||||
<Version>2.1</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
@@ -12,7 +12,7 @@
|
||||
<RootNamespace>WildIp</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>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.</Description>
|
||||
<Description>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.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
23
CHANGELOG.md
23
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.
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ApplicationIcon>logo2.ico</ApplicationIcon>
|
||||
<Version>10.0.1</Version>
|
||||
<Version>11.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare</Authors>
|
||||
|
||||
@@ -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...");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
@@ -11,7 +11,7 @@
|
||||
<RepositoryType></RepositoryType>
|
||||
<Description></Description>
|
||||
<PackageId>DnsServerCore.ApplicationCommon</PackageId>
|
||||
<Version>5.0</Version>
|
||||
<Version>5.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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
|
||||
/// <param name="appRecordName">The domain name of the APP record.</param>
|
||||
/// <param name="appRecordTtl">The TTL value set in the APP record.</param>
|
||||
/// <param name="appRecordData">The record data in the APP record as required for processing the request.</param>
|
||||
/// <returns>The DNS response for the DNS request or <c>null</c> to send no answer response with an SOA authority.</returns>
|
||||
/// <returns>The DNS response for the DNS request or <c>null</c> to send NODATA response when QNAME matches APP record name or else NXDOMAIN response with an SOA authority.</returns>
|
||||
Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <param name="question">The question record containing the details to query.</param>
|
||||
/// <param name="timeout">The timeout value in milliseconds to wait for response.</param>
|
||||
/// <returns>The DNS response for the DNS query.</returns>
|
||||
/// <exception cref="TimeoutException">When request times out.</exception>
|
||||
Task<DnsDatagram> DirectQueryAsync(DnsQuestionRecord question, int timeout = 4000);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="request">The DNS request to query.</param>
|
||||
/// <param name="timeout">The timeout value in milliseconds to wait for response.</param>
|
||||
/// <returns>The DNS response for the DNS query.</returns>
|
||||
/// <exception cref="TimeoutException">When request times out.</exception>
|
||||
Task<DnsDatagram> DirectQueryAsync(DnsDatagram request, int timeout = 4000);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a log entry to the DNS server log file.
|
||||
/// </summary>
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DhcpOption> 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<Lease> 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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
79
DnsServerCore/Dhcp/Options/TftpServerAddressOption.cs
Normal file
79
DnsServerCore/Dhcp/Options/TftpServerAddressOption.cs
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using TechnitiumLibrary.IO;
|
||||
|
||||
namespace DnsServerCore.Dhcp.Options
|
||||
{
|
||||
class TftpServerAddressOption : DhcpOption
|
||||
{
|
||||
#region variables
|
||||
|
||||
IReadOnlyCollection<IPAddress> _addresses;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public TftpServerAddressOption(IReadOnlyCollection<IPAddress> 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<IPAddress> Addresses
|
||||
{ get { return _addresses; } }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
@@ -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<ClasslessStaticRouteOption.Route> _staticRoutes;
|
||||
IReadOnlyDictionary<string, VendorSpecificInformationOption> _vendorInfo;
|
||||
IReadOnlyCollection<IPAddress> _capwapAcIpAddresses;
|
||||
IReadOnlyCollection<IPAddress> _tftpServerAddreses;
|
||||
|
||||
//advanced options
|
||||
IReadOnlyCollection<DhcpOption> _genericOptions;
|
||||
IReadOnlyCollection<Exclusion> _exclusions;
|
||||
readonly ConcurrentDictionary<ClientIdentifierOption, Lease> _reservedLeases = new ConcurrentDictionary<ClientIdentifierOption, Lease>();
|
||||
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<IPAddress> TftpServerAddresses
|
||||
{
|
||||
get { return _tftpServerAddreses; }
|
||||
set
|
||||
{
|
||||
ValidateIpv4(value, nameof(TftpServerAddresses));
|
||||
_tftpServerAddreses = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<DhcpOption> GenericOptions
|
||||
{
|
||||
get { return _genericOptions; }
|
||||
set { _genericOptions = value; }
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<Exclusion> Exclusions
|
||||
{
|
||||
get { return _exclusions; }
|
||||
|
||||
@@ -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<DnsDatagram> DirectQueryAsync(DnsDatagram request, int timeout = 4000)
|
||||
{
|
||||
return _dnsServer.DirectQueryAsync(request, timeout, true);
|
||||
}
|
||||
|
||||
public void WriteLog(string message)
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -201,7 +201,7 @@ namespace DnsServerCore.Dns.Dnssec
|
||||
}
|
||||
}
|
||||
|
||||
public static DnssecPrivateKey Parse(BinaryReader bR)
|
||||
public static DnssecPrivateKey ReadFrom(BinaryReader bR)
|
||||
{
|
||||
if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DK")
|
||||
throw new InvalidDataException("DNSSEC private key format is invalid.");
|
||||
@@ -270,7 +270,7 @@ namespace DnsServerCore.Dns.Dnssec
|
||||
|
||||
byte[] signature = SignHash(hash);
|
||||
|
||||
DnsRRSIGRecordData signedRRSigRecord = new DnsRRSIGRecordData(unsignedRRSigRecord.TypeCovered, unsignedRRSigRecord.Algorithm, unsignedRRSigRecord.Labels, unsignedRRSigRecord.OriginalTtl, unsignedRRSigRecord.SignatureExpirationValue, unsignedRRSigRecord.SignatureInceptionValue, unsignedRRSigRecord.KeyTag, unsignedRRSigRecord.SignersName, signature);
|
||||
DnsRRSIGRecordData signedRRSigRecord = new DnsRRSIGRecordData(unsignedRRSigRecord.TypeCovered, unsignedRRSigRecord.Algorithm, unsignedRRSigRecord.Labels, unsignedRRSigRecord.OriginalTtl, unsignedRRSigRecord.SignatureExpiration, unsignedRRSigRecord.SignatureInception, unsignedRRSigRecord.KeyTag, unsignedRRSigRecord.SignersName, signature);
|
||||
return new DnsResourceRecord(firstRecord.Name, DnsResourceRecordType.RRSIG, firstRecord.Class, firstRecord.OriginalTtlValue, signedRRSigRecord);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -36,17 +36,19 @@ namespace DnsServerCore.Dns
|
||||
readonly AuthZoneManager _authZoneManager;
|
||||
readonly CacheZoneManager _cacheZoneManager;
|
||||
readonly LogManager _log;
|
||||
readonly bool _skipDnsAppAuthoritativeRequestHandlers;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public ResolverDnsCache(DnsApplicationManager dnsApplicationManager, AuthZoneManager authZoneManager, CacheZoneManager cacheZoneManager, LogManager log)
|
||||
public ResolverDnsCache(DnsApplicationManager dnsApplicationManager, AuthZoneManager authZoneManager, CacheZoneManager cacheZoneManager, LogManager log, bool skipDnsAppAuthoritativeRequestHandlers)
|
||||
{
|
||||
_dnsApplicationManager = dnsApplicationManager;
|
||||
_authZoneManager = authZoneManager;
|
||||
_cacheZoneManager = cacheZoneManager;
|
||||
_log = log;
|
||||
_skipDnsAppAuthoritativeRequestHandlers = skipDnsAppAuthoritativeRequestHandlers;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -55,7 +57,7 @@ namespace DnsServerCore.Dns
|
||||
|
||||
private DnsDatagram DnsApplicationQueryClosestDelegation(DnsDatagram request)
|
||||
{
|
||||
if ((_dnsApplicationManager.DnsAuthoritativeRequestHandlers.Count < 1) || (request.Question.Count != 1))
|
||||
if (_skipDnsAppAuthoritativeRequestHandlers || (_dnsApplicationManager.DnsAuthoritativeRequestHandlers.Count < 1) || (request.Question.Count != 1))
|
||||
return null;
|
||||
|
||||
IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 0);
|
||||
@@ -132,27 +134,30 @@ namespace DnsServerCore.Dns
|
||||
{
|
||||
DnsDatagram authResponse = null;
|
||||
|
||||
foreach (IDnsAuthoritativeRequestHandler requestHandler in _dnsApplicationManager.DnsAuthoritativeRequestHandlers)
|
||||
if (!_skipDnsAppAuthoritativeRequestHandlers)
|
||||
{
|
||||
try
|
||||
foreach (IDnsAuthoritativeRequestHandler requestHandler in _dnsApplicationManager.DnsAuthoritativeRequestHandlers)
|
||||
{
|
||||
authResponse = requestHandler.ProcessRequestAsync(request, new IPEndPoint(IPAddress.Any, 0), DnsTransportProtocol.Tcp, false).Sync();
|
||||
if (authResponse is not null)
|
||||
try
|
||||
{
|
||||
if ((authResponse.RCODE != DnsResponseCode.NoError) || (authResponse.Answer.Count > 0) || (authResponse.Authority.Count == 0) || authResponse.IsFirstAuthoritySOA())
|
||||
return authResponse;
|
||||
authResponse = requestHandler.ProcessRequestAsync(request, new IPEndPoint(IPAddress.Any, 0), DnsTransportProtocol.Tcp, true).Sync();
|
||||
if (authResponse is not null)
|
||||
{
|
||||
if ((authResponse.RCODE != DnsResponseCode.NoError) || (authResponse.Answer.Count > 0) || (authResponse.Authority.Count == 0) || authResponse.IsFirstAuthoritySOA())
|
||||
return authResponse;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_log is not null)
|
||||
_log.Write(ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_log is not null)
|
||||
_log.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (authResponse is null)
|
||||
{
|
||||
authResponse = _authZoneManager.Query(request, true);
|
||||
authResponse = _authZoneManager.Query(request);
|
||||
if (authResponse is not null)
|
||||
{
|
||||
if ((authResponse.RCODE != DnsResponseCode.NoError) || (authResponse.Answer.Count > 0) || (authResponse.Authority.Count == 0) || authResponse.IsFirstAuthoritySOA())
|
||||
|
||||
@@ -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,8 +33,8 @@ namespace DnsServerCore.Dns
|
||||
|
||||
#region constructor
|
||||
|
||||
public ResolverPrefetchDnsCache(DnsApplicationManager dnsApplicationManager, AuthZoneManager authZoneManager, CacheZoneManager cacheZoneManager, LogManager log, DnsQuestionRecord prefetchQuestion)
|
||||
: base(dnsApplicationManager, authZoneManager, cacheZoneManager, log)
|
||||
public ResolverPrefetchDnsCache(DnsApplicationManager dnsApplicationManager, AuthZoneManager authZoneManager, CacheZoneManager cacheZoneManager, LogManager log, bool skipDnsAppAuthoritativeRequestHandlers, DnsQuestionRecord prefetchQuestion)
|
||||
: base(dnsApplicationManager, authZoneManager, cacheZoneManager, log, skipDnsAppAuthoritativeRequestHandlers)
|
||||
{
|
||||
_prefetchQuestion = prefetchQuestion;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -22,16 +22,17 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using TechnitiumLibrary.IO;
|
||||
using TechnitiumLibrary.Net;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
namespace DnsServerCore.Dns.ResourceRecords
|
||||
{
|
||||
class DnsResourceRecordInfo
|
||||
class AuthRecordInfo
|
||||
{
|
||||
#region variables
|
||||
|
||||
public static readonly AuthRecordInfo Default = new AuthRecordInfo();
|
||||
|
||||
bool _disabled;
|
||||
IReadOnlyList<DnsResourceRecord> _glueRecords;
|
||||
string _comments;
|
||||
@@ -40,19 +41,16 @@ namespace DnsServerCore.Dns.ResourceRecords
|
||||
DnsTransportProtocol _zoneTransferProtocol;
|
||||
string _tsigKeyName = string.Empty;
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> _rrsigRecords; //not serialized
|
||||
IReadOnlyList<DnsResourceRecord> _nsecRecords; //not serialized
|
||||
NetworkAddress _eDnsClientSubnet; //not serialized
|
||||
DateTime _lastUsedOn; //not serialized
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public DnsResourceRecordInfo()
|
||||
public AuthRecordInfo()
|
||||
{ }
|
||||
|
||||
public DnsResourceRecordInfo(BinaryReader bR, bool isSoa)
|
||||
public AuthRecordInfo(BinaryReader bR, bool isSoa)
|
||||
{
|
||||
byte version = bR.ReadByte();
|
||||
switch (version)
|
||||
@@ -155,7 +153,7 @@ namespace DnsServerCore.Dns.ResourceRecords
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("DnsResourceRecordInfo format version not supported.");
|
||||
throw new InvalidDataException("AuthRecordInfo format version not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +215,13 @@ namespace DnsServerCore.Dns.ResourceRecords
|
||||
public IReadOnlyList<DnsResourceRecord> GlueRecords
|
||||
{
|
||||
get { return _glueRecords; }
|
||||
set { _glueRecords = value; }
|
||||
set
|
||||
{
|
||||
if ((value is null) || (value.Count == 0))
|
||||
_glueRecords = null;
|
||||
else
|
||||
_glueRecords = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Comments
|
||||
@@ -241,7 +245,13 @@ namespace DnsServerCore.Dns.ResourceRecords
|
||||
public IReadOnlyList<NameServerAddress> PrimaryNameServers
|
||||
{
|
||||
get { return _primaryNameServers; }
|
||||
set { _primaryNameServers = value; }
|
||||
set
|
||||
{
|
||||
if ((value is null) || (value.Count == 0))
|
||||
_primaryNameServers = null;
|
||||
else
|
||||
_primaryNameServers = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DnsTransportProtocol ZoneTransferProtocol
|
||||
@@ -253,6 +263,7 @@ namespace DnsServerCore.Dns.ResourceRecords
|
||||
{
|
||||
case DnsTransportProtocol.Tcp:
|
||||
case DnsTransportProtocol.Tls:
|
||||
case DnsTransportProtocol.Quic:
|
||||
_zoneTransferProtocol = value;
|
||||
break;
|
||||
|
||||
@@ -274,24 +285,6 @@ namespace DnsServerCore.Dns.ResourceRecords
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> RRSIGRecords
|
||||
{
|
||||
get { return _rrsigRecords; }
|
||||
set { _rrsigRecords = value; }
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> NSECRecords
|
||||
{
|
||||
get { return _nsecRecords; }
|
||||
set { _nsecRecords = value; }
|
||||
}
|
||||
|
||||
public NetworkAddress EDnsClientSubnet
|
||||
{
|
||||
get { return _eDnsClientSubnet; }
|
||||
set { _eDnsClientSubnet = value; }
|
||||
}
|
||||
|
||||
public DateTime LastUsedOn
|
||||
{
|
||||
get { return _lastUsedOn; }
|
||||
199
DnsServerCore/Dns/ResourceRecords/CacheRecordInfo.cs
Normal file
199
DnsServerCore/Dns/ResourceRecords/CacheRecordInfo.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using TechnitiumLibrary.Net;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
namespace DnsServerCore.Dns.ResourceRecords
|
||||
{
|
||||
class CacheRecordInfo
|
||||
{
|
||||
#region variables
|
||||
|
||||
public static readonly CacheRecordInfo Default = new CacheRecordInfo();
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> _glueRecords;
|
||||
IReadOnlyList<DnsResourceRecord> _rrsigRecords;
|
||||
IReadOnlyList<DnsResourceRecord> _nsecRecords;
|
||||
NetworkAddress _eDnsClientSubnet;
|
||||
|
||||
DateTime _lastUsedOn; //not serialized
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public CacheRecordInfo()
|
||||
{ }
|
||||
|
||||
public CacheRecordInfo(BinaryReader bR)
|
||||
{
|
||||
byte version = bR.ReadByte();
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
_glueRecords = ReadRecordsFrom(bR, true);
|
||||
_rrsigRecords = ReadRecordsFrom(bR, false);
|
||||
_nsecRecords = ReadRecordsFrom(bR, true);
|
||||
|
||||
if (bR.ReadBoolean())
|
||||
_eDnsClientSubnet = NetworkAddress.ReadFrom(bR);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("CacheRecordInfo format version not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
|
||||
private static IReadOnlyList<DnsResourceRecord> ReadRecordsFrom(BinaryReader bR, bool includeInnerRRSigRecords)
|
||||
{
|
||||
int count = bR.ReadByte();
|
||||
if (count == 0)
|
||||
return null;
|
||||
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
records[i] = DnsResourceRecord.ReadCacheRecordFrom(bR, delegate (DnsResourceRecord record)
|
||||
{
|
||||
if (includeInnerRRSigRecords)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> rrsigRecords = ReadRecordsFrom(bR, false);
|
||||
if (rrsigRecords is not null)
|
||||
record.GetCacheRecordInfo()._rrsigRecords = rrsigRecords;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private static void WriteRecordsTo(IReadOnlyList<DnsResourceRecord> records, BinaryWriter bW, bool includeInnerRRSigRecords)
|
||||
{
|
||||
if (records is null)
|
||||
{
|
||||
bW.Write((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
bW.Write(Convert.ToByte(records.Count));
|
||||
|
||||
foreach (DnsResourceRecord record in records)
|
||||
{
|
||||
record.WriteCacheRecordTo(bW, delegate ()
|
||||
{
|
||||
if (includeInnerRRSigRecords)
|
||||
{
|
||||
if (record.Tag is CacheRecordInfo cacheRecordInfo)
|
||||
WriteRecordsTo(cacheRecordInfo._rrsigRecords, bW, false);
|
||||
else
|
||||
bW.Write((byte)0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public
|
||||
|
||||
public void WriteTo(BinaryWriter bW)
|
||||
{
|
||||
bW.Write((byte)1); //version
|
||||
|
||||
WriteRecordsTo(_glueRecords, bW, true);
|
||||
WriteRecordsTo(_rrsigRecords, bW, false);
|
||||
WriteRecordsTo(_nsecRecords, bW, true);
|
||||
|
||||
if (_eDnsClientSubnet is null)
|
||||
{
|
||||
bW.Write(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
bW.Write(true);
|
||||
_eDnsClientSubnet.WriteTo(bW);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> GlueRecords
|
||||
{
|
||||
get { return _glueRecords; }
|
||||
set
|
||||
{
|
||||
if ((value is null) || (value.Count == 0))
|
||||
_glueRecords = null;
|
||||
else
|
||||
_glueRecords = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> RRSIGRecords
|
||||
{
|
||||
get { return _rrsigRecords; }
|
||||
set
|
||||
{
|
||||
if ((value is null) || (value.Count == 0))
|
||||
_rrsigRecords = null;
|
||||
else
|
||||
_rrsigRecords = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> NSECRecords
|
||||
{
|
||||
get { return _nsecRecords; }
|
||||
set
|
||||
{
|
||||
if ((value is null) || (value.Count == 0))
|
||||
_nsecRecords = null;
|
||||
else
|
||||
_nsecRecords = value;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkAddress EDnsClientSubnet
|
||||
{
|
||||
get { return _eDnsClientSubnet; }
|
||||
set { _eDnsClientSubnet = value; }
|
||||
}
|
||||
|
||||
public DateTime LastUsedOn
|
||||
{
|
||||
get { return _lastUsedOn; }
|
||||
set { _lastUsedOn = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
/*
|
||||
Technitium DNS Server
|
||||
Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
namespace DnsServerCore.Dns.ResourceRecords
|
||||
{
|
||||
static class DnsResourceRecordExtension
|
||||
{
|
||||
public static void SetGlueRecords(this DnsResourceRecord record, IReadOnlyList<DnsResourceRecord> glueRecords)
|
||||
{
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
{
|
||||
rrInfo = new DnsResourceRecordInfo();
|
||||
record.Tag = rrInfo;
|
||||
}
|
||||
|
||||
rrInfo.GlueRecords = glueRecords;
|
||||
}
|
||||
|
||||
public static void SetGlueRecords(this DnsResourceRecord record, string glueAddresses)
|
||||
{
|
||||
string[] addresses = glueAddresses.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
List<IPAddress> ipAddresses = new List<IPAddress>(addresses.Length);
|
||||
|
||||
foreach (string address in addresses)
|
||||
ipAddresses.Add(IPAddress.Parse(address.Trim()));
|
||||
|
||||
SetGlueRecords(record, ipAddresses);
|
||||
}
|
||||
|
||||
public static void SetGlueRecords(this DnsResourceRecord record, IReadOnlyList<IPAddress> glueAddresses)
|
||||
{
|
||||
if (record.RDATA is not DnsNSRecordData nsRecord)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
string domain = nsRecord.NameServer;
|
||||
|
||||
DnsResourceRecord[] glueRecords = new DnsResourceRecord[glueAddresses.Count];
|
||||
|
||||
for (int i = 0; i < glueRecords.Length; i++)
|
||||
{
|
||||
switch (glueAddresses[i].AddressFamily)
|
||||
{
|
||||
case AddressFamily.InterNetwork:
|
||||
glueRecords[i] = new DnsResourceRecord(domain, DnsResourceRecordType.A, DnsClass.IN, record.TtlValue, new DnsARecordData(glueAddresses[i]));
|
||||
break;
|
||||
|
||||
case AddressFamily.InterNetworkV6:
|
||||
glueRecords[i] = new DnsResourceRecord(domain, DnsResourceRecordType.AAAA, DnsClass.IN, record.TtlValue, new DnsAAAARecordData(glueAddresses[i]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SetGlueRecords(record, glueRecords);
|
||||
}
|
||||
|
||||
public static void SyncGlueRecords(this DnsResourceRecord record, IReadOnlyList<DnsResourceRecord> allGlueRecords)
|
||||
{
|
||||
if (record.RDATA is not DnsNSRecordData nsRecord)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
string domain = nsRecord.NameServer;
|
||||
|
||||
List<DnsResourceRecord> foundGlueRecords = new List<DnsResourceRecord>(2);
|
||||
|
||||
foreach (DnsResourceRecord glueRecord in allGlueRecords)
|
||||
{
|
||||
switch (glueRecord.Type)
|
||||
{
|
||||
case DnsResourceRecordType.A:
|
||||
case DnsResourceRecordType.AAAA:
|
||||
if (glueRecord.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
|
||||
foundGlueRecords.Add(glueRecord);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundGlueRecords.Count > 0)
|
||||
SetGlueRecords(record, foundGlueRecords);
|
||||
else
|
||||
SetGlueRecords(record, Array.Empty<DnsResourceRecord>());
|
||||
}
|
||||
|
||||
public static void SyncGlueRecords(this DnsResourceRecord record, IReadOnlyCollection<DnsResourceRecord> deletedGlueRecords, IReadOnlyCollection<DnsResourceRecord> addedGlueRecords)
|
||||
{
|
||||
if (record.RDATA is not DnsNSRecordData nsRecord)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
bool updated = false;
|
||||
|
||||
List<DnsResourceRecord> updatedGlueRecords = new List<DnsResourceRecord>();
|
||||
IReadOnlyList<DnsResourceRecord> existingGlueRecords = GetGlueRecords(record);
|
||||
|
||||
foreach (DnsResourceRecord existingGlueRecord in existingGlueRecords)
|
||||
{
|
||||
if (deletedGlueRecords.Contains(existingGlueRecord))
|
||||
updated = true; //skipped to delete existing glue record
|
||||
else
|
||||
updatedGlueRecords.Add(existingGlueRecord);
|
||||
}
|
||||
|
||||
string domain = nsRecord.NameServer;
|
||||
|
||||
foreach (DnsResourceRecord addedGlueRecord in addedGlueRecords)
|
||||
{
|
||||
switch (addedGlueRecord.Type)
|
||||
{
|
||||
case DnsResourceRecordType.A:
|
||||
case DnsResourceRecordType.AAAA:
|
||||
if (addedGlueRecord.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
updatedGlueRecords.Add(addedGlueRecord);
|
||||
updated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
SetGlueRecords(record, updatedGlueRecords);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<DnsResourceRecord> GetGlueRecords(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is DnsResourceRecordInfo rrInfo)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = rrInfo.GlueRecords;
|
||||
if (glueRecords is null)
|
||||
return Array.Empty<DnsResourceRecord>();
|
||||
|
||||
return glueRecords;
|
||||
}
|
||||
|
||||
return Array.Empty<DnsResourceRecord>();
|
||||
}
|
||||
|
||||
public static bool IsDisabled(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is DnsResourceRecordInfo rrInfo)
|
||||
return rrInfo.Disabled;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Disable(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
{
|
||||
rrInfo = new DnsResourceRecordInfo();
|
||||
record.Tag = rrInfo;
|
||||
}
|
||||
|
||||
rrInfo.Disabled = true;
|
||||
}
|
||||
|
||||
public static void Enable(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is DnsResourceRecordInfo rrInfo)
|
||||
rrInfo.Disabled = false;
|
||||
}
|
||||
|
||||
public static string GetComments(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is DnsResourceRecordInfo rrInfo)
|
||||
return rrInfo.Comments;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void SetComments(this DnsResourceRecord record, string value)
|
||||
{
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
{
|
||||
rrInfo = new DnsResourceRecordInfo();
|
||||
record.Tag = rrInfo;
|
||||
}
|
||||
|
||||
rrInfo.Comments = value;
|
||||
}
|
||||
|
||||
public static DateTime GetDeletedOn(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is DnsResourceRecordInfo rrInfo)
|
||||
return rrInfo.DeletedOn;
|
||||
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
|
||||
public static void SetDeletedOn(this DnsResourceRecord record, DateTime value)
|
||||
{
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
{
|
||||
rrInfo = new DnsResourceRecordInfo();
|
||||
record.Tag = rrInfo;
|
||||
}
|
||||
|
||||
rrInfo.DeletedOn = value;
|
||||
}
|
||||
|
||||
public static void SetPrimaryNameServers(this DnsResourceRecord record, IReadOnlyList<NameServerAddress> primaryNameServers)
|
||||
{
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
{
|
||||
rrInfo = new DnsResourceRecordInfo();
|
||||
record.Tag = rrInfo;
|
||||
}
|
||||
|
||||
rrInfo.PrimaryNameServers = primaryNameServers;
|
||||
}
|
||||
|
||||
public static void SetPrimaryNameServers(this DnsResourceRecord record, string primaryNameServers)
|
||||
{
|
||||
string[] nameServerAddresses = primaryNameServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
List<NameServerAddress> nameServers = new List<NameServerAddress>(nameServerAddresses.Length);
|
||||
|
||||
foreach (string nameServerAddress in nameServerAddresses)
|
||||
nameServers.Add(new NameServerAddress(nameServerAddress));
|
||||
|
||||
SetPrimaryNameServers(record, nameServers);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<NameServerAddress> GetPrimaryNameServers(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is DnsResourceRecordInfo rrInfo)
|
||||
{
|
||||
IReadOnlyList<NameServerAddress> primaryNameServers = rrInfo.PrimaryNameServers;
|
||||
if (primaryNameServers is null)
|
||||
return Array.Empty<NameServerAddress>();
|
||||
|
||||
return primaryNameServers;
|
||||
}
|
||||
|
||||
return Array.Empty<NameServerAddress>();
|
||||
}
|
||||
|
||||
public static DnsResourceRecordInfo GetRecordInfo(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
{
|
||||
rrInfo = new DnsResourceRecordInfo();
|
||||
record.Tag = rrInfo;
|
||||
}
|
||||
|
||||
return rrInfo;
|
||||
}
|
||||
|
||||
public static void CopyRecordInfoFrom(this DnsResourceRecord record, DnsResourceRecord otherRecord)
|
||||
{
|
||||
record.Tag = otherRecord.Tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
152
DnsServerCore/Dns/ResourceRecords/DnsResourceRecordExtensions.cs
Normal file
152
DnsServerCore/Dns/ResourceRecords/DnsResourceRecordExtensions.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using TechnitiumLibrary;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
namespace DnsServerCore.Dns.ResourceRecords
|
||||
{
|
||||
static class DnsResourceRecordExtensions
|
||||
{
|
||||
public static void SetGlueRecords(this DnsResourceRecord record, string glueAddresses)
|
||||
{
|
||||
if (record.RDATA is not DnsNSRecordData nsRecord)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
string domain = nsRecord.NameServer;
|
||||
|
||||
IReadOnlyList<IPAddress> glueAddressesList = glueAddresses.Split(IPAddress.Parse, ',');
|
||||
DnsResourceRecord[] glueRecords = new DnsResourceRecord[glueAddressesList.Count];
|
||||
|
||||
for (int i = 0; i < glueRecords.Length; i++)
|
||||
{
|
||||
switch (glueAddressesList[i].AddressFamily)
|
||||
{
|
||||
case AddressFamily.InterNetwork:
|
||||
glueRecords[i] = new DnsResourceRecord(domain, DnsResourceRecordType.A, DnsClass.IN, record.TTL, new DnsARecordData(glueAddressesList[i]));
|
||||
break;
|
||||
|
||||
case AddressFamily.InterNetworkV6:
|
||||
glueRecords[i] = new DnsResourceRecord(domain, DnsResourceRecordType.AAAA, DnsClass.IN, record.TTL, new DnsAAAARecordData(glueAddressesList[i]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
record.GetAuthRecordInfo().GlueRecords = glueRecords;
|
||||
}
|
||||
|
||||
public static void SyncGlueRecords(this DnsResourceRecord record, IReadOnlyList<DnsResourceRecord> allGlueRecords)
|
||||
{
|
||||
if (record.RDATA is not DnsNSRecordData nsRecord)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
string domain = nsRecord.NameServer;
|
||||
|
||||
List<DnsResourceRecord> foundGlueRecords = new List<DnsResourceRecord>(2);
|
||||
|
||||
foreach (DnsResourceRecord glueRecord in allGlueRecords)
|
||||
{
|
||||
switch (glueRecord.Type)
|
||||
{
|
||||
case DnsResourceRecordType.A:
|
||||
case DnsResourceRecordType.AAAA:
|
||||
if (glueRecord.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
|
||||
foundGlueRecords.Add(glueRecord);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
record.GetAuthRecordInfo().GlueRecords = foundGlueRecords;
|
||||
}
|
||||
|
||||
public static void SyncGlueRecords(this DnsResourceRecord record, IReadOnlyCollection<DnsResourceRecord> deletedGlueRecords, IReadOnlyCollection<DnsResourceRecord> addedGlueRecords)
|
||||
{
|
||||
if (record.RDATA is not DnsNSRecordData nsRecord)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
bool updated = false;
|
||||
|
||||
List<DnsResourceRecord> updatedGlueRecords = new List<DnsResourceRecord>();
|
||||
IReadOnlyList<DnsResourceRecord> existingGlueRecords = record.GetAuthRecordInfo().GlueRecords;
|
||||
if (existingGlueRecords is not null)
|
||||
{
|
||||
foreach (DnsResourceRecord existingGlueRecord in existingGlueRecords)
|
||||
{
|
||||
if (deletedGlueRecords.Contains(existingGlueRecord))
|
||||
updated = true; //skipped to delete existing glue record
|
||||
else
|
||||
updatedGlueRecords.Add(existingGlueRecord);
|
||||
}
|
||||
}
|
||||
|
||||
string domain = nsRecord.NameServer;
|
||||
|
||||
foreach (DnsResourceRecord addedGlueRecord in addedGlueRecords)
|
||||
{
|
||||
switch (addedGlueRecord.Type)
|
||||
{
|
||||
case DnsResourceRecordType.A:
|
||||
case DnsResourceRecordType.AAAA:
|
||||
if (addedGlueRecord.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
updatedGlueRecords.Add(addedGlueRecord);
|
||||
updated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
record.GetAuthRecordInfo().GlueRecords = updatedGlueRecords;
|
||||
}
|
||||
|
||||
public static AuthRecordInfo GetAuthRecordInfo(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is not AuthRecordInfo rrInfo)
|
||||
{
|
||||
rrInfo = new AuthRecordInfo();
|
||||
record.Tag = rrInfo;
|
||||
}
|
||||
|
||||
return rrInfo;
|
||||
}
|
||||
|
||||
public static CacheRecordInfo GetCacheRecordInfo(this DnsResourceRecord record)
|
||||
{
|
||||
if (record.Tag is not CacheRecordInfo rrInfo)
|
||||
{
|
||||
rrInfo = new CacheRecordInfo();
|
||||
record.Tag = rrInfo;
|
||||
}
|
||||
|
||||
return rrInfo;
|
||||
}
|
||||
|
||||
public static void CopyRecordInfoFrom(this DnsResourceRecord record, DnsResourceRecord otherRecord)
|
||||
{
|
||||
record.Tag = otherRecord.Tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -1388,7 +1388,7 @@ namespace DnsServerCore.Dns
|
||||
_clientIpAddresses = new ConcurrentDictionary<IPAddress, Counter>(1, count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
_clientIpAddresses.TryAdd(IPAddressExtension.ReadFrom(bR), new Counter(bR.ReadInt32()));
|
||||
_clientIpAddresses.TryAdd(IPAddressExtensions.ReadFrom(bR), new Counter(bR.ReadInt32()));
|
||||
|
||||
if (version < 6)
|
||||
_totalClients = count;
|
||||
@@ -1413,7 +1413,7 @@ namespace DnsServerCore.Dns
|
||||
_errorIpAddresses = new ConcurrentDictionary<IPAddress, Counter>(1, count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
_errorIpAddresses.TryAdd(IPAddressExtension.ReadFrom(bR), new Counter(bR.ReadInt32()));
|
||||
_errorIpAddresses.TryAdd(IPAddressExtensions.ReadFrom(bR), new Counter(bR.ReadInt32()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1465,7 +1465,7 @@ namespace DnsServerCore.Dns
|
||||
_clientIpAddresses = new ConcurrentDictionary<IPAddress, Counter>(1, count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
_clientIpAddresses.TryAdd(IPAddressExtension.ReadFrom(bR), new Counter(bR.ReadInt64()));
|
||||
_clientIpAddresses.TryAdd(IPAddressExtensions.ReadFrom(bR), new Counter(bR.ReadInt64()));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1481,7 +1481,7 @@ namespace DnsServerCore.Dns
|
||||
_errorIpAddresses = new ConcurrentDictionary<IPAddress, Counter>(1, count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
_errorIpAddresses.TryAdd(IPAddressExtension.ReadFrom(bR), new Counter(bR.ReadInt64()));
|
||||
_errorIpAddresses.TryAdd(IPAddressExtensions.ReadFrom(bR), new Counter(bR.ReadInt64()));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1537,10 +1537,21 @@ namespace DnsServerCore.Dns
|
||||
switch (responseCode)
|
||||
{
|
||||
case DnsResponseCode.NoError:
|
||||
if ((query is not null) && (responseType != DnsServerResponseType.Blocked)) //skip blocked domains
|
||||
if (query is not null)
|
||||
{
|
||||
_queryDomains.GetOrAdd(query.Name.ToLower(), GetNewCounter).Increment();
|
||||
_queries.GetOrAdd(query, GetNewCounter).Increment();
|
||||
switch (responseType)
|
||||
{
|
||||
case DnsServerResponseType.Blocked:
|
||||
case DnsServerResponseType.UpstreamBlocked:
|
||||
case DnsServerResponseType.CacheBlocked:
|
||||
//skip blocked domains
|
||||
break;
|
||||
|
||||
default:
|
||||
_queryDomains.GetOrAdd(query.Name.ToLower(), GetNewCounter).Increment();
|
||||
_queries.GetOrAdd(query, GetNewCounter).Increment();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _totalNoError);
|
||||
@@ -1585,6 +1596,24 @@ namespace DnsServerCore.Dns
|
||||
|
||||
Interlocked.Increment(ref _totalBlocked);
|
||||
break;
|
||||
|
||||
case DnsServerResponseType.UpstreamBlocked:
|
||||
Interlocked.Increment(ref _totalRecursive);
|
||||
|
||||
if (query is not null)
|
||||
_queryBlockedDomains.GetOrAdd(query.Name.ToLower(), GetNewCounter).Increment();
|
||||
|
||||
Interlocked.Increment(ref _totalBlocked);
|
||||
break;
|
||||
|
||||
case DnsServerResponseType.CacheBlocked:
|
||||
Interlocked.Increment(ref _totalCached);
|
||||
|
||||
if (query is not null)
|
||||
_queryBlockedDomains.GetOrAdd(query.Name.ToLower(), GetNewCounter).Increment();
|
||||
|
||||
Interlocked.Increment(ref _totalBlocked);
|
||||
break;
|
||||
}
|
||||
|
||||
if (query is not null)
|
||||
|
||||
@@ -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
|
||||
@@ -137,14 +137,14 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
_zoneManager.Flush();
|
||||
}
|
||||
|
||||
public List<AuthZoneInfo> ListZones()
|
||||
public IReadOnlyList<AuthZoneInfo> GetAllZones()
|
||||
{
|
||||
return _zoneManager.ListZones();
|
||||
return _zoneManager.GetAllZones();
|
||||
}
|
||||
|
||||
public void ListAllRecords(string domain, List<DnsResourceRecord> records)
|
||||
{
|
||||
_zoneManager.ListAllRecords(domain, records);
|
||||
_zoneManager.ListAllRecords(domain, domain, records);
|
||||
}
|
||||
|
||||
public void ListSubDomains(string domain, List<string> subDomains)
|
||||
@@ -154,7 +154,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
public void SaveZoneFile()
|
||||
{
|
||||
List<AuthZoneInfo> allowedZones = _dnsServer.AllowedZoneManager.ListZones();
|
||||
IReadOnlyList<AuthZoneInfo> allowedZones = _dnsServer.AllowedZoneManager.GetAllZones();
|
||||
|
||||
string allowedZoneFile = Path.Combine(_dnsServer.ConfigFolder, "allowed.config");
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
public DnsDatagram Query(DnsDatagram request)
|
||||
{
|
||||
return _zoneManager.Query(request, true);
|
||||
return _zoneManager.Query(request);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -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
|
||||
@@ -45,7 +45,8 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
readonly AuthZoneTree _root = new AuthZoneTree();
|
||||
|
||||
int _totalZones;
|
||||
readonly List<AuthZoneInfo> _zoneIndex = new List<AuthZoneInfo>(10);
|
||||
readonly ReaderWriterLockSlim _zoneIndexLock = new ReaderWriterLockSlim();
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -94,7 +95,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
//update authoritative zone SOA and NS records
|
||||
try
|
||||
{
|
||||
List<AuthZoneInfo> zones = ListZones();
|
||||
IReadOnlyList<AuthZoneInfo> zones = GetAllZones();
|
||||
|
||||
foreach (AuthZoneInfo zone in zones)
|
||||
{
|
||||
@@ -110,7 +111,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
if (responsiblePerson.EndsWith(_serverDomain))
|
||||
responsiblePerson = responsiblePerson.Replace(_serverDomain, serverDomain);
|
||||
|
||||
SetRecords(zone.Name, record.Name, record.Type, record.TtlValue, new DnsResourceRecordData[] { new DnsSOARecordData(serverDomain, responsiblePerson, soa.Serial, soa.Refresh, soa.Retry, soa.Expire, soa.Minimum) });
|
||||
SetRecords(zone.Name, record.Name, record.Type, record.TTL, new DnsResourceRecordData[] { new DnsSOARecordData(serverDomain, responsiblePerson, soa.Serial, soa.Refresh, soa.Retry, soa.Expire, soa.Minimum) });
|
||||
|
||||
//update NS records
|
||||
IReadOnlyList<DnsResourceRecord> nsResourceRecords = zone.GetApexRecords(DnsResourceRecordType.NS);
|
||||
@@ -119,7 +120,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
if ((nsResourceRecord.RDATA as DnsNSRecordData).NameServer.Equals(_serverDomain, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
UpdateRecord(zone.Name, nsResourceRecord, new DnsResourceRecord(nsResourceRecord.Name, nsResourceRecord.Type, nsResourceRecord.Class, nsResourceRecord.TtlValue, new DnsNSRecordData(serverDomain)) { Tag = nsResourceRecord.Tag });
|
||||
UpdateRecord(zone.Name, nsResourceRecord, new DnsResourceRecord(nsResourceRecord.Name, nsResourceRecord.Type, nsResourceRecord.Class, nsResourceRecord.TTL, new DnsNSRecordData(serverDomain)) { Tag = nsResourceRecord.Tag });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -179,10 +180,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
|
||||
if (_root.TryAdd(zone))
|
||||
{
|
||||
_totalZones++;
|
||||
return zone;
|
||||
}
|
||||
|
||||
throw new DnsServerException("Zone already exists: " + zoneInfo.Name);
|
||||
}
|
||||
@@ -301,7 +299,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
if (DnsClient.IsDomainNameValid(result))
|
||||
{
|
||||
DnsResourceRecord cnameRR = new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, question.Class, dnameRR.TtlValue, new DnsCNAMERecordData(result));
|
||||
DnsResourceRecord cnameRR = new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, question.Class, dnameRR.TTL, new DnsCNAMERecordData(result));
|
||||
|
||||
List<DnsResourceRecord> list = new List<DnsResourceRecord>(5);
|
||||
|
||||
@@ -329,8 +327,8 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
switch (refRecord.Type)
|
||||
{
|
||||
case DnsResourceRecordType.NS:
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = refRecord.GetGlueRecords();
|
||||
if (glueRecords.Count > 0)
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = refRecord.GetAuthRecordInfo().GlueRecords;
|
||||
if (glueRecords is not null)
|
||||
{
|
||||
additionalRecords.AddRange(glueRecords);
|
||||
}
|
||||
@@ -371,7 +369,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
}
|
||||
|
||||
private DnsDatagram GetReferralResponse(DnsDatagram request, bool dnssecOk, AuthZone delegationZone, ApexZone apexZone, bool isRecursionAllowed)
|
||||
private DnsDatagram GetReferralResponse(DnsDatagram request, bool dnssecOk, AuthZone delegationZone, ApexZone apexZone)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> authority;
|
||||
|
||||
@@ -383,7 +381,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
foreach (DnsResourceRecord record in authority)
|
||||
record.GetRecordInfo().LastUsedOn = utcNow;
|
||||
record.GetAuthRecordInfo().LastUsedOn = utcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -426,17 +424,17 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> additional = GetAdditionalRecords(authority, dnssecOk);
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional);
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional);
|
||||
}
|
||||
|
||||
private DnsDatagram GetForwarderResponse(DnsDatagram request, AuthZone zone, AuthZone closestZone, ApexZone forwarderZone, bool isRecursionAllowed)
|
||||
private DnsDatagram GetForwarderResponse(DnsDatagram request, AuthZone zone, AuthZone closestZone, ApexZone forwarderZone)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> authority = null;
|
||||
|
||||
if (zone is not null)
|
||||
{
|
||||
if (zone.ContainsNameServerRecords())
|
||||
return GetReferralResponse(request, false, zone, forwarderZone, isRecursionAllowed);
|
||||
return GetReferralResponse(request, false, zone, forwarderZone);
|
||||
|
||||
authority = zone.QueryRecords(DnsResourceRecordType.FWD, false);
|
||||
}
|
||||
@@ -444,7 +442,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
if (((authority is null) || (authority.Count == 0)) && (closestZone is not null))
|
||||
{
|
||||
if (closestZone.ContainsNameServerRecords())
|
||||
return GetReferralResponse(request, false, closestZone, forwarderZone, isRecursionAllowed);
|
||||
return GetReferralResponse(request, false, closestZone, forwarderZone);
|
||||
|
||||
authority = closestZone.QueryRecords(DnsResourceRecordType.FWD, false);
|
||||
}
|
||||
@@ -452,17 +450,26 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
if ((authority is null) || (authority.Count == 0))
|
||||
{
|
||||
if (forwarderZone.ContainsNameServerRecords())
|
||||
return GetReferralResponse(request, false, forwarderZone, forwarderZone, isRecursionAllowed);
|
||||
return GetReferralResponse(request, false, forwarderZone, forwarderZone);
|
||||
|
||||
authority = forwarderZone.QueryRecords(DnsResourceRecordType.FWD, false);
|
||||
}
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, null, authority);
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, authority);
|
||||
}
|
||||
|
||||
internal void Flush()
|
||||
{
|
||||
_root.Clear();
|
||||
_zoneIndexLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_root.Clear();
|
||||
_zoneIndex.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<DnsResourceRecord> CondenseIncrementalZoneTransferRecords(string zoneName, DnsResourceRecord currentSoaRecord, IReadOnlyList<DnsResourceRecord> xfrRecords)
|
||||
@@ -652,7 +659,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
public void LoadAllZoneFiles()
|
||||
{
|
||||
_root.Clear();
|
||||
Flush();
|
||||
|
||||
string zonesFolder = Path.Combine(_dnsServer.ConfigFolder, "zones");
|
||||
if (!Directory.Exists(zonesFolder))
|
||||
@@ -666,7 +673,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
File.Move(oldZoneFile, Path.Combine(zonesFolder, Path.GetFileName(oldZoneFile)));
|
||||
}
|
||||
|
||||
//remove old internal zones
|
||||
//remove old internal zones files
|
||||
{
|
||||
string[] oldZoneFiles = new string[] { "localhost.zone", "1.0.0.127.in-addr.arpa.zone", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.zone" };
|
||||
|
||||
@@ -722,27 +729,38 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
|
||||
//load zone files
|
||||
string[] zoneFiles = Directory.GetFiles(zonesFolder, "*.zone");
|
||||
|
||||
foreach (string zoneFile in zoneFiles)
|
||||
_zoneIndexLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
LoadZoneFrom(fS);
|
||||
}
|
||||
string[] zoneFiles = Directory.GetFiles(zonesFolder, "*.zone");
|
||||
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server successfully loaded zone file: " + zoneFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
foreach (string zoneFile in zoneFiles)
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server failed to load zone file: " + zoneFile + "\r\n" + ex.ToString());
|
||||
try
|
||||
{
|
||||
using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
AuthZoneInfo zoneInfo = LoadZoneFrom(fS);
|
||||
_zoneIndex.Add(zoneInfo);
|
||||
}
|
||||
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server successfully loaded zone file: " + zoneFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server failed to load zone file: " + zoneFile + "\r\n" + ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
_zoneIndex.Sort();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,10 +768,21 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
PrimaryZone apexZone = new PrimaryZone(_dnsServer, zoneName, soaRecord, ns);
|
||||
|
||||
if (_root.TryAdd(apexZone))
|
||||
_zoneIndexLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_totalZones++;
|
||||
return new AuthZoneInfo(apexZone);
|
||||
if (_root.TryAdd(apexZone))
|
||||
{
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
|
||||
_zoneIndex.Add(zoneInfo);
|
||||
_zoneIndex.Sort();
|
||||
|
||||
return zoneInfo;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -763,10 +792,21 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
PrimaryZone apexZone = new PrimaryZone(_dnsServer, zoneName, primaryNameServer, @internal);
|
||||
|
||||
if (_root.TryAdd(apexZone))
|
||||
_zoneIndexLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_totalZones++;
|
||||
return new AuthZoneInfo(apexZone);
|
||||
if (_root.TryAdd(apexZone))
|
||||
{
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
|
||||
_zoneIndex.Add(zoneInfo);
|
||||
_zoneIndex.Sort();
|
||||
|
||||
return zoneInfo;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -776,11 +816,23 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
SecondaryZone apexZone = await SecondaryZone.CreateAsync(_dnsServer, zoneName, primaryNameServerAddresses, zoneTransferProtocol, tsigKeyName);
|
||||
|
||||
if (_root.TryAdd(apexZone))
|
||||
_zoneIndexLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
apexZone.TriggerRefresh(0);
|
||||
_totalZones++;
|
||||
return new AuthZoneInfo(apexZone);
|
||||
if (_root.TryAdd(apexZone))
|
||||
{
|
||||
apexZone.TriggerRefresh(0);
|
||||
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
|
||||
_zoneIndex.Add(zoneInfo);
|
||||
_zoneIndex.Sort();
|
||||
|
||||
return zoneInfo;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -790,11 +842,23 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
StubZone apexZone = await StubZone.CreateAsync(_dnsServer, zoneName, primaryNameServerAddresses);
|
||||
|
||||
if (_root.TryAdd(apexZone))
|
||||
_zoneIndexLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
apexZone.TriggerRefresh(0);
|
||||
_totalZones++;
|
||||
return new AuthZoneInfo(apexZone);
|
||||
if (_root.TryAdd(apexZone))
|
||||
{
|
||||
apexZone.TriggerRefresh(0);
|
||||
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
|
||||
_zoneIndex.Add(zoneInfo);
|
||||
_zoneIndex.Sort();
|
||||
|
||||
return zoneInfo;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -804,10 +868,21 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
ForwarderZone apexZone = new ForwarderZone(zoneName, forwarderProtocol, forwarder, dnssecValidation, proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword, fwdRecordComments);
|
||||
|
||||
if (_root.TryAdd(apexZone))
|
||||
_zoneIndexLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_totalZones++;
|
||||
return new AuthZoneInfo(apexZone);
|
||||
if (_root.TryAdd(apexZone))
|
||||
{
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
|
||||
_zoneIndex.Add(zoneInfo);
|
||||
_zoneIndex.Sort();
|
||||
|
||||
return zoneInfo;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -943,12 +1018,23 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
public bool DeleteZone(string zoneName)
|
||||
{
|
||||
if (_root.TryRemove(zoneName, out ApexZone apexZone))
|
||||
_zoneIndexLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
apexZone.Dispose();
|
||||
if (_root.TryRemove(zoneName, out ApexZone apexZone))
|
||||
{
|
||||
apexZone.Dispose();
|
||||
|
||||
_totalZones--;
|
||||
return true;
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
|
||||
if (!_zoneIndex.Remove(zoneInfo))
|
||||
throw new InvalidOperationException("Zone deleted from tree but failed to remove from zone index.");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -978,12 +1064,20 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
return _root.TryGet(zoneName, domain, out _);
|
||||
}
|
||||
|
||||
public void ListAllRecords(string zoneName, List<DnsResourceRecord> records)
|
||||
public void ListAllZoneRecords(string zoneName, List<DnsResourceRecord> records)
|
||||
{
|
||||
foreach (AuthZone zone in _root.GetZoneWithSubDomainZones(zoneName))
|
||||
zone.ListAllRecords(records);
|
||||
}
|
||||
|
||||
public void ListAllRecords(string zoneName, string domain, List<DnsResourceRecord> records)
|
||||
{
|
||||
ValidateZoneNameFor(zoneName, domain);
|
||||
|
||||
if (_root.TryGet(zoneName, domain, out AuthZone authZone))
|
||||
authZone.ListAllRecords(records);
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> GetRecords(string zoneName, string domain, DnsResourceRecordType type)
|
||||
{
|
||||
ValidateZoneNameFor(zoneName, domain);
|
||||
@@ -1018,7 +1112,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
DnsResourceRecord soaRecord = soaRecords[0];
|
||||
|
||||
List<DnsResourceRecord> records = new List<DnsResourceRecord>();
|
||||
ListAllRecords(zoneName, records);
|
||||
ListAllZoneRecords(zoneName, records);
|
||||
|
||||
List<DnsResourceRecord> xfrRecords = new List<DnsResourceRecord>(records.Count + 1);
|
||||
|
||||
@@ -1027,7 +1121,8 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
foreach (DnsResourceRecord record in records)
|
||||
{
|
||||
if (record.IsDisabled())
|
||||
AuthRecordInfo authRecordInfo = record.GetAuthRecordInfo();
|
||||
if (authRecordInfo.Disabled)
|
||||
continue;
|
||||
|
||||
switch (record.Type)
|
||||
@@ -1038,9 +1133,12 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
case DnsResourceRecordType.NS:
|
||||
xfrRecords.Add(record);
|
||||
|
||||
foreach (DnsResourceRecord glueRecord in record.GetGlueRecords())
|
||||
xfrRecords.Add(glueRecord);
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = authRecordInfo.GlueRecords;
|
||||
if (glueRecords is not null)
|
||||
{
|
||||
foreach (DnsResourceRecord glueRecord in glueRecords)
|
||||
xfrRecords.Add(glueRecord);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1187,7 +1285,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
//sync records
|
||||
List<DnsResourceRecord> currentRecords = new List<DnsResourceRecord>();
|
||||
ListAllRecords(zoneName, currentRecords);
|
||||
ListAllZoneRecords(zoneName, currentRecords);
|
||||
|
||||
Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> currentRecordsGroupedByDomain = DnsResourceRecord.GroupRecords(currentRecords);
|
||||
Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> latestRecordsGroupedByDomain = DnsResourceRecord.GroupRecords(latestRecords);
|
||||
@@ -1686,31 +1784,47 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
}
|
||||
|
||||
public List<AuthZoneInfo> ListZones()
|
||||
public IReadOnlyList<AuthZoneInfo> GetAllZones()
|
||||
{
|
||||
List<AuthZoneInfo> zones = new List<AuthZoneInfo>();
|
||||
|
||||
foreach (AuthZoneNode zoneNode in _root)
|
||||
_zoneIndexLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
ApexZone apexZone = zoneNode.ApexZone;
|
||||
if (apexZone is null)
|
||||
continue;
|
||||
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
case AuthZoneType.Secondary:
|
||||
case AuthZoneType.Stub:
|
||||
case AuthZoneType.Forwarder:
|
||||
zones.Add(zoneInfo);
|
||||
break;
|
||||
}
|
||||
return new List<AuthZoneInfo>(_zoneIndex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
_totalZones = zones.Count;
|
||||
public ZonesPage GetZonesPage(int pageNumber, int zonesPerPage)
|
||||
{
|
||||
_zoneIndexLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (pageNumber == 0)
|
||||
pageNumber = 1;
|
||||
|
||||
return zones;
|
||||
int totalZones = _zoneIndex.Count;
|
||||
int totalPages = (totalZones / zonesPerPage) + (totalZones % zonesPerPage > 0 ? 1 : 0);
|
||||
|
||||
if ((pageNumber > totalPages) || (pageNumber < 0))
|
||||
pageNumber = totalPages;
|
||||
|
||||
int start = (pageNumber - 1) * zonesPerPage;
|
||||
int end = Math.Min(start + zonesPerPage, totalZones);
|
||||
|
||||
List<AuthZoneInfo> zones = new List<AuthZoneInfo>(end - start);
|
||||
|
||||
for (int i = start; i < end; i++)
|
||||
zones.Add(_zoneIndex[i]);
|
||||
|
||||
return new ZonesPage(pageNumber, totalPages, totalZones, zones);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_zoneIndexLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void ListSubDomains(string domain, List<string> subDomains)
|
||||
@@ -1725,14 +1839,14 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
bool dnssecOk = request.DnssecOk && (apexZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned);
|
||||
|
||||
return GetReferralResponse(request, dnssecOk, delegation, apexZone, true);
|
||||
return GetReferralResponse(request, dnssecOk, delegation, apexZone);
|
||||
}
|
||||
|
||||
//no delegation found
|
||||
return null;
|
||||
}
|
||||
|
||||
public DnsDatagram Query(DnsDatagram request, bool isRecursionAllowed)
|
||||
public DnsDatagram Query(DnsDatagram request)
|
||||
{
|
||||
DnsQuestionRecord question = request.Question[0];
|
||||
|
||||
@@ -1747,10 +1861,10 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
//zone not found
|
||||
if ((delegation is not null) && delegation.IsActive && (delegation.Name.Length > apexZone.Name.Length))
|
||||
return GetReferralResponse(request, dnssecOk, delegation, apexZone, isRecursionAllowed);
|
||||
return GetReferralResponse(request, dnssecOk, delegation, apexZone);
|
||||
|
||||
if (apexZone is StubZone)
|
||||
return GetReferralResponse(request, false, apexZone, apexZone, isRecursionAllowed);
|
||||
return GetReferralResponse(request, false, apexZone, apexZone);
|
||||
|
||||
DnsResponseCode rCode = DnsResponseCode.NoError;
|
||||
IReadOnlyList<DnsResourceRecord> answer = null;
|
||||
@@ -1786,7 +1900,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
if (authority.Count == 0)
|
||||
{
|
||||
if (apexZone is ForwarderZone)
|
||||
return GetForwarderResponse(request, null, closest, apexZone, isRecursionAllowed); //no DNAME or APP record available so process FWD response
|
||||
return GetForwarderResponse(request, null, closest, apexZone); //no DNAME or APP record available so process FWD response
|
||||
|
||||
if (!hasSubDomains)
|
||||
rCode = DnsResponseCode.NxDomain;
|
||||
@@ -1817,7 +1931,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
}
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, false, rCode, request.Question, answer, authority);
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, rCode, request.Question, answer, authority);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1835,7 +1949,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
else if (zone.Equals(delegation))
|
||||
{
|
||||
//zone is delegation
|
||||
return GetReferralResponse(request, dnssecOk, delegation, apexZone, isRecursionAllowed);
|
||||
return GetReferralResponse(request, dnssecOk, delegation, apexZone);
|
||||
}
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> authority = null;
|
||||
@@ -1865,10 +1979,10 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
//check for delegation, stub & forwarder
|
||||
if ((delegation is not null) && delegation.IsActive && (delegation.Name.Length > apexZone.Name.Length))
|
||||
return GetReferralResponse(request, dnssecOk, delegation, apexZone, isRecursionAllowed);
|
||||
return GetReferralResponse(request, dnssecOk, delegation, apexZone);
|
||||
|
||||
if (apexZone is StubZone)
|
||||
return GetReferralResponse(request, false, apexZone, apexZone, isRecursionAllowed);
|
||||
return GetReferralResponse(request, false, apexZone, apexZone);
|
||||
}
|
||||
|
||||
authority = zone.QueryRecords(DnsResourceRecordType.APP, false);
|
||||
@@ -1883,7 +1997,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
if (authority.Count == 0)
|
||||
{
|
||||
if (apexZone is ForwarderZone)
|
||||
return GetForwarderResponse(request, zone, closest, apexZone, isRecursionAllowed); //no APP record available so process FWD response
|
||||
return GetForwarderResponse(request, zone, closest, apexZone); //no APP record available so process FWD response
|
||||
|
||||
authority = apexZone.QueryRecords(DnsResourceRecordType.SOA, dnssecOk);
|
||||
|
||||
@@ -1922,7 +2036,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
DnsResourceRecord[] wildcardAnswers = new DnsResourceRecord[answers.Count];
|
||||
|
||||
for (int i = 0; i < answers.Count; i++)
|
||||
wildcardAnswers[i] = new DnsResourceRecord(question.Name, answers[i].Type, answers[i].Class, answers[i].TtlValue, answers[i].RDATA) { Tag = answers[i].Tag };
|
||||
wildcardAnswers[i] = new DnsResourceRecord(question.Name, answers[i].Type, answers[i].Class, answers[i].TTL, answers[i].RDATA) { Tag = answers[i].Tag };
|
||||
|
||||
answers = wildcardAnswers;
|
||||
|
||||
@@ -1975,7 +2089,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
}
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, authority, additional);
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, authority, additional);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2006,7 +2120,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadZoneFrom(Stream s)
|
||||
public AuthZoneInfo LoadZoneFrom(Stream s)
|
||||
{
|
||||
BinaryReader bR = new BinaryReader(s);
|
||||
|
||||
@@ -2018,108 +2132,110 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
case 2:
|
||||
{
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
|
||||
if (records.Length > 0)
|
||||
if (records.Length == 0)
|
||||
throw new InvalidDataException("Zone does not contain SOA record.");
|
||||
|
||||
DnsResourceRecord soaRecord = null;
|
||||
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
DnsResourceRecord soaRecord = null;
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
|
||||
if (records[i].Type == DnsResourceRecordType.SOA)
|
||||
soaRecord = records[i];
|
||||
}
|
||||
|
||||
if (soaRecord == null)
|
||||
throw new InvalidDataException("Zone does not contain SOA record.");
|
||||
|
||||
//make zone info
|
||||
AuthZoneType zoneType;
|
||||
if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase))
|
||||
zoneType = AuthZoneType.Primary;
|
||||
else
|
||||
zoneType = AuthZoneType.Stub;
|
||||
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, false);
|
||||
|
||||
//create zone
|
||||
ApexZone apexZone = CreateEmptyZone(zoneInfo);
|
||||
|
||||
try
|
||||
{
|
||||
//load records
|
||||
LoadRecords(apexZone, records);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DeleteZone(zoneInfo.Name);
|
||||
throw;
|
||||
}
|
||||
|
||||
//init zone
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
(apexZone as PrimaryZone).TriggerNotify();
|
||||
break;
|
||||
}
|
||||
if (records[i].Type == DnsResourceRecordType.SOA)
|
||||
soaRecord = records[i];
|
||||
}
|
||||
|
||||
if (soaRecord == null)
|
||||
throw new InvalidDataException("Zone does not contain SOA record.");
|
||||
|
||||
//make zone info
|
||||
AuthZoneType zoneType;
|
||||
if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase))
|
||||
zoneType = AuthZoneType.Primary;
|
||||
else
|
||||
zoneType = AuthZoneType.Stub;
|
||||
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, false);
|
||||
|
||||
//create zone
|
||||
ApexZone apexZone = CreateEmptyZone(zoneInfo);
|
||||
|
||||
try
|
||||
{
|
||||
//load records
|
||||
LoadRecords(apexZone, records);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DeleteZone(zoneInfo.Name);
|
||||
throw;
|
||||
}
|
||||
|
||||
//init zone
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
(apexZone as PrimaryZone).TriggerNotify();
|
||||
break;
|
||||
}
|
||||
|
||||
return new AuthZoneInfo(apexZone);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
bool zoneDisabled = bR.ReadBoolean();
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
|
||||
if (records.Length > 0)
|
||||
if (records.Length == 0)
|
||||
throw new InvalidDataException("Zone does not contain SOA record.");
|
||||
|
||||
DnsResourceRecord soaRecord = null;
|
||||
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
DnsResourceRecord soaRecord = null;
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
records[i].Tag = new AuthRecordInfo(bR, records[i].Type == DnsResourceRecordType.SOA);
|
||||
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
records[i].Tag = new DnsResourceRecordInfo(bR, records[i].Type == DnsResourceRecordType.SOA);
|
||||
|
||||
if (records[i].Type == DnsResourceRecordType.SOA)
|
||||
soaRecord = records[i];
|
||||
}
|
||||
|
||||
if (soaRecord == null)
|
||||
throw new InvalidDataException("Zone does not contain SOA record.");
|
||||
|
||||
//make zone info
|
||||
AuthZoneType zoneType;
|
||||
if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase))
|
||||
zoneType = AuthZoneType.Primary;
|
||||
else
|
||||
zoneType = AuthZoneType.Stub;
|
||||
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, zoneDisabled);
|
||||
|
||||
//create zone
|
||||
ApexZone apexZone = CreateEmptyZone(zoneInfo);
|
||||
|
||||
try
|
||||
{
|
||||
//load records
|
||||
LoadRecords(apexZone, records);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DeleteZone(zoneInfo.Name);
|
||||
throw;
|
||||
}
|
||||
|
||||
//init zone
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
(apexZone as PrimaryZone).TriggerNotify();
|
||||
break;
|
||||
}
|
||||
if (records[i].Type == DnsResourceRecordType.SOA)
|
||||
soaRecord = records[i];
|
||||
}
|
||||
|
||||
if (soaRecord == null)
|
||||
throw new InvalidDataException("Zone does not contain SOA record.");
|
||||
|
||||
//make zone info
|
||||
AuthZoneType zoneType;
|
||||
if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase))
|
||||
zoneType = AuthZoneType.Primary;
|
||||
else
|
||||
zoneType = AuthZoneType.Stub;
|
||||
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, zoneDisabled);
|
||||
|
||||
//create zone
|
||||
ApexZone apexZone = CreateEmptyZone(zoneInfo);
|
||||
|
||||
try
|
||||
{
|
||||
//load records
|
||||
LoadRecords(apexZone, records);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DeleteZone(zoneInfo.Name);
|
||||
throw;
|
||||
}
|
||||
|
||||
//init zone
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
(apexZone as PrimaryZone).TriggerNotify();
|
||||
break;
|
||||
}
|
||||
|
||||
return new AuthZoneInfo(apexZone);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
{
|
||||
@@ -2136,7 +2252,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
records[i].Tag = new DnsResourceRecordInfo(bR, records[i].Type == DnsResourceRecordType.SOA);
|
||||
records[i].Tag = new AuthRecordInfo(bR, records[i].Type == DnsResourceRecordType.SOA);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -2169,8 +2285,9 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new AuthZoneInfo(apexZone);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("DNS Zone file version not supported.");
|
||||
@@ -2197,7 +2314,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
//write all zone records
|
||||
List<DnsResourceRecord> records = new List<DnsResourceRecord>();
|
||||
ListAllRecords(zoneName, records);
|
||||
ListAllZoneRecords(zoneName, records);
|
||||
|
||||
bW.Write(records.Count);
|
||||
|
||||
@@ -2205,8 +2322,8 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
record.WriteTo(s);
|
||||
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
rrInfo = new DnsResourceRecordInfo(); //default info
|
||||
if (record.Tag is not AuthRecordInfo rrInfo)
|
||||
rrInfo = AuthRecordInfo.Default; //default info
|
||||
|
||||
rrInfo.WriteTo(bW);
|
||||
}
|
||||
@@ -2257,8 +2374,48 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
|
||||
public int TotalZones
|
||||
{ get { return _totalZones; } }
|
||||
{ get { return _zoneIndex.Count; } }
|
||||
|
||||
#endregion
|
||||
|
||||
public class ZonesPage
|
||||
{
|
||||
#region variables
|
||||
|
||||
readonly long _pageNumber;
|
||||
readonly long _totalPages;
|
||||
readonly long _totalZones;
|
||||
readonly IReadOnlyList<AuthZoneInfo> _zones;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public ZonesPage(long pageNumber, long totalPages, long totalZones, IReadOnlyList<AuthZoneInfo> zones)
|
||||
{
|
||||
_pageNumber = pageNumber;
|
||||
_totalPages = totalPages;
|
||||
_totalZones = totalZones;
|
||||
_zones = zones;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
public long PageNumber
|
||||
{ get { return _pageNumber; } }
|
||||
|
||||
public long TotalPages
|
||||
{ get { return _totalPages; } }
|
||||
|
||||
public long TotalZones
|
||||
{ get { return _totalZones; } }
|
||||
|
||||
public IReadOnlyList<AuthZoneInfo> Zones
|
||||
{ get { return _zones; } }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -27,6 +27,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TechnitiumLibrary.Net;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.EDnsOptions;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
namespace DnsServerCore.Dns.ZoneManagers
|
||||
@@ -35,6 +36,8 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
#region variables
|
||||
|
||||
readonly static char[] _popWordSeperator = new char[] { ' ', '\t' };
|
||||
|
||||
readonly DnsServer _dnsServer;
|
||||
readonly string _localCacheFolder;
|
||||
|
||||
@@ -87,9 +90,9 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
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)
|
||||
@@ -106,92 +109,138 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
return word;
|
||||
}
|
||||
|
||||
private Queue<string> ReadListFile(Uri listUrl, bool isAllowList)
|
||||
private Queue<string> ReadListFile(Uri listUrl, bool isAllowList, Dictionary<string, object> allowedDomains)
|
||||
{
|
||||
Queue<string> domains = new Queue<string>();
|
||||
|
||||
try
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server is reading " + (isAllowList ? "allow" : "block") + " list from: " + listUrl.AbsoluteUri);
|
||||
_dnsServer.LogManager?.Write("DNS Server is reading " + (isAllowList ? "allow" : "block") + " list from: " + listUrl.AbsoluteUri);
|
||||
|
||||
using (FileStream fS = new FileStream(GetBlockListFilePath(listUrl), FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
//parse hosts file and populate block zone
|
||||
StreamReader sR = new StreamReader(fS, true);
|
||||
char[] trimSeperator = new char[] { ' ', '\t', '*', '.' };
|
||||
string line;
|
||||
string firstWord;
|
||||
string secondWord;
|
||||
string hostname;
|
||||
string domain;
|
||||
string options;
|
||||
int i;
|
||||
|
||||
while (true)
|
||||
{
|
||||
line = sR.ReadLine();
|
||||
if (line == null)
|
||||
if (line is null)
|
||||
break; //eof
|
||||
|
||||
line = line.TrimStart(' ', '\t');
|
||||
line = line.TrimStart(trimSeperator);
|
||||
|
||||
if (line.Length == 0)
|
||||
continue; //skip empty line
|
||||
|
||||
if (line.StartsWith("#"))
|
||||
if (line.StartsWith("#") || line.StartsWith("!"))
|
||||
continue; //skip comment line
|
||||
|
||||
firstWord = PopWord(ref line);
|
||||
|
||||
if (line.Length == 0)
|
||||
if (line.StartsWith("||"))
|
||||
{
|
||||
hostname = firstWord;
|
||||
//adblock format
|
||||
i = line.IndexOf('^');
|
||||
if (i > -1)
|
||||
{
|
||||
domain = line.Substring(2, i - 2);
|
||||
options = line.Substring(i + 1);
|
||||
|
||||
if (((options.Length == 0) || (options.StartsWith("$") && (options.Contains("doc") || options.Contains("all")))) && DnsClient.IsDomainNameValid(domain))
|
||||
domains.Enqueue(domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
domain = line.Substring(2);
|
||||
|
||||
if (DnsClient.IsDomainNameValid(domain))
|
||||
domains.Enqueue(domain);
|
||||
}
|
||||
}
|
||||
else if (line.StartsWith("@@||"))
|
||||
{
|
||||
//adblock format
|
||||
if (!isAllowList)
|
||||
{
|
||||
i = line.IndexOf('^');
|
||||
if (i > -1)
|
||||
{
|
||||
domain = line.Substring(4, i - 4);
|
||||
options = line.Substring(i + 1);
|
||||
|
||||
if (((options.Length == 0) || (options.StartsWith("$") && (options.Contains("doc") || options.Contains("all")))) && DnsClient.IsDomainNameValid(domain))
|
||||
allowedDomains.TryAdd(domain, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
domain = line.Substring(4);
|
||||
|
||||
if (DnsClient.IsDomainNameValid(domain))
|
||||
allowedDomains.TryAdd(domain, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
secondWord = PopWord(ref line);
|
||||
//hosts file format
|
||||
firstWord = PopWord(ref line);
|
||||
|
||||
if (secondWord.Length == 0)
|
||||
if (line.Length == 0)
|
||||
{
|
||||
hostname = firstWord;
|
||||
}
|
||||
else
|
||||
hostname = secondWord;
|
||||
{
|
||||
secondWord = PopWord(ref line);
|
||||
|
||||
if (secondWord.Length == 0)
|
||||
hostname = firstWord;
|
||||
else
|
||||
hostname = secondWord;
|
||||
}
|
||||
|
||||
hostname = hostname.Trim('.').ToLower();
|
||||
|
||||
switch (hostname)
|
||||
{
|
||||
case "":
|
||||
case "localhost":
|
||||
case "localhost.localdomain":
|
||||
case "local":
|
||||
case "broadcasthost":
|
||||
case "ip6-localhost":
|
||||
case "ip6-loopback":
|
||||
case "ip6-localnet":
|
||||
case "ip6-mcastprefix":
|
||||
case "ip6-allnodes":
|
||||
case "ip6-allrouters":
|
||||
case "ip6-allhosts":
|
||||
continue; //skip these hostnames
|
||||
}
|
||||
|
||||
if (!DnsClient.IsDomainNameValid(hostname))
|
||||
continue;
|
||||
|
||||
if (IPAddress.TryParse(hostname, out _))
|
||||
continue; //skip line when hostname is IP address
|
||||
|
||||
domains.Enqueue(hostname);
|
||||
}
|
||||
|
||||
hostname = hostname.Trim('.').ToLower();
|
||||
|
||||
switch (hostname)
|
||||
{
|
||||
case "":
|
||||
case "localhost":
|
||||
case "localhost.localdomain":
|
||||
case "local":
|
||||
case "broadcasthost":
|
||||
case "ip6-localhost":
|
||||
case "ip6-loopback":
|
||||
case "ip6-localnet":
|
||||
case "ip6-mcastprefix":
|
||||
case "ip6-allnodes":
|
||||
case "ip6-allrouters":
|
||||
case "ip6-allhosts":
|
||||
continue; //skip these hostnames
|
||||
}
|
||||
|
||||
if (!DnsClient.IsDomainNameValid(hostname))
|
||||
continue;
|
||||
|
||||
if (IPAddress.TryParse(hostname, out _))
|
||||
continue; //skip line when hostname is IP address
|
||||
|
||||
domains.Enqueue(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
if (log != null)
|
||||
log.Write("DNS Server read " + (isAllowList ? "allow" : "block") + " list file (" + domains.Count + " domains) from: " + listUrl.AbsoluteUri);
|
||||
_dnsServer.LogManager?.Write("DNS Server read " + (isAllowList ? "allow" : "block") + " list file (" + domains.Count + " domains) from: " + listUrl.AbsoluteUri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server failed to read " + (isAllowList ? "allow" : "block") + " list from: " + listUrl.AbsoluteUri + "\r\n" + ex.ToString());
|
||||
_dnsServer.LogManager?.Write("DNS Server failed to read " + (isAllowList ? "allow" : "block") + " list from: " + listUrl.AbsoluteUri + "\r\n" + ex.ToString());
|
||||
}
|
||||
|
||||
return domains;
|
||||
@@ -243,7 +292,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
foreach (Uri allowListUrl in _allowListUrls)
|
||||
{
|
||||
Queue<string> queue = ReadListFile(allowListUrl, true);
|
||||
Queue<string> queue = ReadListFile(allowListUrl, true, null);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
@@ -261,7 +310,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
if (!blockListQueues.ContainsKey(blockListUrl))
|
||||
{
|
||||
Queue<string> blockListQueue = ReadListFile(blockListUrl, false);
|
||||
Queue<string> blockListQueue = ReadListFile(blockListUrl, false, allowedDomains);
|
||||
totalDomains += blockListQueue.Count;
|
||||
blockListQueues.Add(blockListUrl, blockListQueue);
|
||||
}
|
||||
@@ -322,7 +371,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
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))
|
||||
{
|
||||
@@ -419,10 +468,20 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
for (int i = 0; i < answer.Length; i++)
|
||||
answer[i] = new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, 60, new DnsTXTRecordData("source=block-list-zone; blockListUrl=" + blockLists[i].AbsoluteUri + "; domain=" + blockedDomain));
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, answer);
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answer);
|
||||
}
|
||||
else
|
||||
{
|
||||
EDnsOption[] options = null;
|
||||
|
||||
if (_dnsServer.AllowTxtBlockingReport && (request.EDNS is not null))
|
||||
{
|
||||
options = new EDnsOption[blockLists.Count];
|
||||
|
||||
for (int i = 0; i < options.Length; i++)
|
||||
options[i] = new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.Blocked, "source=block-list-zone; blockListUrl=" + blockLists[i].AbsoluteUri + "; domain=" + blockedDomain));
|
||||
}
|
||||
|
||||
IReadOnlyCollection<DnsARecordData> aRecords;
|
||||
IReadOnlyCollection<DnsAAAARecordData> aaaaRecords;
|
||||
|
||||
@@ -443,7 +502,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
if (parentDomain is null)
|
||||
parentDomain = string.Empty;
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NxDomain, request.Question, null, new DnsResourceRecord[] { new DnsResourceRecord(parentDomain, DnsResourceRecordType.SOA, question.Class, 60, _soaRecord) });
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NxDomain, request.Question, null, new DnsResourceRecord[] { new DnsResourceRecord(parentDomain, DnsResourceRecordType.SOA, question.Class, 60, _soaRecord) }, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options);
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
@@ -493,7 +552,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
break;
|
||||
}
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, answer, authority);
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answer, authority, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -155,14 +155,14 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
_zoneManager.Flush();
|
||||
}
|
||||
|
||||
public List<AuthZoneInfo> ListZones()
|
||||
public IReadOnlyList<AuthZoneInfo> GetAllZones()
|
||||
{
|
||||
return _zoneManager.ListZones();
|
||||
return _zoneManager.GetAllZones();
|
||||
}
|
||||
|
||||
public void ListAllRecords(string domain, List<DnsResourceRecord> records)
|
||||
{
|
||||
_zoneManager.ListAllRecords(domain, records);
|
||||
_zoneManager.ListAllRecords(domain, domain, records);
|
||||
}
|
||||
|
||||
public void ListSubDomains(string domain, List<string> subDomains)
|
||||
@@ -172,7 +172,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
public void SaveZoneFile()
|
||||
{
|
||||
List<AuthZoneInfo> blockedZones = _dnsServer.BlockedZoneManager.ListZones();
|
||||
IReadOnlyList<AuthZoneInfo> blockedZones = _dnsServer.BlockedZoneManager.GetAllZones();
|
||||
|
||||
string blockedZoneFile = Path.Combine(_dnsServer.ConfigFolder, "blocked.config");
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
public DnsDatagram Query(DnsDatagram request)
|
||||
{
|
||||
return _zoneManager.Query(request, true);
|
||||
return _zoneManager.Query(request);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -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
|
||||
@@ -22,6 +22,8 @@ using DnsServerCore.Dns.Trees;
|
||||
using DnsServerCore.Dns.Zones;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using TechnitiumLibrary.Net;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
@@ -80,7 +82,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
if ((glueRecords is not null) || (rrsigRecords is not null) || (nsecRecords is not null) || (eDnsClientSubnet is not null))
|
||||
{
|
||||
DnsResourceRecordInfo rrInfo = resourceRecord.GetRecordInfo();
|
||||
CacheRecordInfo rrInfo = resourceRecord.GetCacheRecordInfo();
|
||||
|
||||
rrInfo.GlueRecords = glueRecords;
|
||||
rrInfo.RRSIGRecords = rrsigRecords;
|
||||
@@ -93,7 +95,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> glueRRSIGRecords = GetRRSIGRecordsFrom(glueRecord);
|
||||
if (glueRRSIGRecords is not null)
|
||||
glueRecord.GetRecordInfo().RRSIGRecords = glueRRSIGRecords;
|
||||
glueRecord.GetCacheRecordInfo().RRSIGRecords = glueRRSIGRecords;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +105,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> nsecRRSIGRecords = GetRRSIGRecordsFrom(nsecRecord);
|
||||
if (nsecRRSIGRecords is not null)
|
||||
nsecRecord.GetRecordInfo().RRSIGRecords = nsecRRSIGRecords;
|
||||
nsecRecord.GetCacheRecordInfo().RRSIGRecords = nsecRRSIGRecords;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +193,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
}
|
||||
|
||||
//no DS records found check for NSEC records
|
||||
IReadOnlyList<DnsResourceRecord> nsecRecords = nsRecords[0].GetRecordInfo().NSECRecords;
|
||||
IReadOnlyList<DnsResourceRecord> nsecRecords = nsRecords[0].GetCacheRecordInfo().NSECRecords;
|
||||
if (nsecRecords is not null)
|
||||
{
|
||||
List<DnsResourceRecord> newNSRecords = new List<DnsResourceRecord>(nsRecords.Count + nsecRecords.Count);
|
||||
@@ -215,7 +217,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
newAnswerList.Add(record);
|
||||
|
||||
DnsResourceRecordInfo rrInfo = record.GetRecordInfo();
|
||||
CacheRecordInfo rrInfo = record.GetCacheRecordInfo();
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> rrsigRecords = rrInfo.RRSIGRecords;
|
||||
if (rrsigRecords is not null)
|
||||
@@ -238,7 +240,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
newAuthorityList.Add(nsecRecord);
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> nsecRRSIGRecords = nsecRecord.GetRecordInfo().RRSIGRecords;
|
||||
IReadOnlyList<DnsResourceRecord> nsecRRSIGRecords = nsecRecord.GetCacheRecordInfo().RRSIGRecords;
|
||||
if (nsecRRSIGRecords is not null)
|
||||
newAuthorityList.AddRange(nsecRRSIGRecords);
|
||||
}
|
||||
@@ -299,7 +301,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
if (DnsClient.IsDomainNameValid(result))
|
||||
{
|
||||
DnsResourceRecord cnameRR = new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, question.Class, dnameRR.TtlValue, new DnsCNAMERecordData(result));
|
||||
DnsResourceRecord cnameRR = new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, question.Class, dnameRR.TTL, new DnsCNAMERecordData(result));
|
||||
|
||||
List<DnsResourceRecord> list = new List<DnsResourceRecord>(5)
|
||||
{
|
||||
@@ -355,8 +357,8 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
private void ResolveAdditionalRecords(DnsResourceRecord refRecord, string domain, bool serveStale, bool dnssecOk, List<DnsResourceRecord> additionalRecords)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = refRecord.GetGlueRecords();
|
||||
if (glueRecords.Count > 0)
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = refRecord.GetCacheRecordInfo().GlueRecords;
|
||||
if (glueRecords is not null)
|
||||
{
|
||||
bool added = false;
|
||||
|
||||
@@ -369,7 +371,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
if (dnssecOk)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> rrsigRecords = glueRecord.GetRecordInfo().RRSIGRecords;
|
||||
IReadOnlyList<DnsResourceRecord> rrsigRecords = glueRecord.GetCacheRecordInfo().RRSIGRecords;
|
||||
if (rrsigRecords is not null)
|
||||
additionalRecords.AddRange(rrsigRecords);
|
||||
}
|
||||
@@ -591,7 +593,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
|
||||
if (requestECS is not null)
|
||||
eDnsClientSubnet = new NetworkAddress(requestECS.AddressValue, requestECS.SourcePrefixLength);
|
||||
eDnsClientSubnet = new NetworkAddress(requestECS.Address, requestECS.SourcePrefixLength);
|
||||
}
|
||||
|
||||
CacheZone zone;
|
||||
@@ -668,10 +670,10 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption(true);
|
||||
if (requestECS is not null)
|
||||
{
|
||||
NetworkAddress recordECS = firstRR.GetRecordInfo().EDnsClientSubnet;
|
||||
NetworkAddress recordECS = firstRR.GetCacheRecordInfo().EDnsClientSubnet;
|
||||
if (recordECS is not null)
|
||||
{
|
||||
EDnsOption[] ecsOption = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, recordECS.PrefixLength, requestECS.AddressValue);
|
||||
EDnsOption[] ecsOption = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, recordECS.PrefixLength, requestECS.Address);
|
||||
|
||||
if ((specialOptions is null) || (specialOptions.Count == 0))
|
||||
{
|
||||
@@ -798,7 +800,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
foreach (DnsResourceRecord record in answer)
|
||||
{
|
||||
NetworkAddress recordECS = record.GetRecordInfo().EDnsClientSubnet;
|
||||
NetworkAddress recordECS = record.GetCacheRecordInfo().EDnsClientSubnet;
|
||||
if (recordECS is not null)
|
||||
{
|
||||
if ((suitableECS is null) || (recordECS.PrefixLength > suitableECS.PrefixLength))
|
||||
@@ -808,7 +810,7 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
|
||||
if (suitableECS is not null)
|
||||
{
|
||||
EDnsOption[] ecsOption = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, suitableECS.PrefixLength, requestECS.AddressValue);
|
||||
EDnsOption[] ecsOption = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, suitableECS.PrefixLength, requestECS.Address);
|
||||
|
||||
if (options is null)
|
||||
{
|
||||
@@ -950,6 +952,85 @@ namespace DnsServerCore.Dns.ZoneManagers
|
||||
return null;
|
||||
}
|
||||
|
||||
public void LoadCacheZoneFile()
|
||||
{
|
||||
string cacheZoneFile = Path.Combine(_dnsServer.ConfigFolder, "cache.bin");
|
||||
|
||||
if (!File.Exists(cacheZoneFile))
|
||||
return;
|
||||
|
||||
_dnsServer.LogManager?.Write("Loading DNS Cache from disk...");
|
||||
|
||||
using (FileStream fS = new FileStream(cacheZoneFile, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
BinaryReader bR = new BinaryReader(fS);
|
||||
|
||||
if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "CZ")
|
||||
throw new InvalidDataException("CacheZoneManager format is invalid.");
|
||||
|
||||
int version = bR.ReadByte();
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
int addedEntries = 0;
|
||||
|
||||
try
|
||||
{
|
||||
bool serveStale = _dnsServer.ServeStale;
|
||||
|
||||
while (bR.BaseStream.Position < bR.BaseStream.Length)
|
||||
{
|
||||
CacheZone zone = CacheZone.ReadFrom(bR, serveStale);
|
||||
if (!zone.IsEmpty)
|
||||
{
|
||||
if (_root.TryAdd(zone.Name, zone))
|
||||
addedEntries += zone.TotalEntries;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedEntries > 0)
|
||||
Interlocked.Add(ref _totalEntries, addedEntries);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("CacheZoneManager format version not supported: " + version);
|
||||
}
|
||||
}
|
||||
|
||||
_dnsServer.LogManager?.Write("DNS Cache was loaded from disk successfully.");
|
||||
}
|
||||
|
||||
public void SaveCacheZoneFile()
|
||||
{
|
||||
_dnsServer.LogManager?.Write("Saving DNS Cache to disk...");
|
||||
|
||||
string cacheZoneFile = Path.Combine(_dnsServer.ConfigFolder, "cache.bin");
|
||||
|
||||
using (FileStream fS = new FileStream(cacheZoneFile, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
BinaryWriter bW = new BinaryWriter(fS);
|
||||
|
||||
bW.Write(Encoding.ASCII.GetBytes("CZ")); //format
|
||||
bW.Write((byte)1); //version
|
||||
|
||||
foreach (CacheZone zone in _root)
|
||||
zone.WriteTo(bW);
|
||||
}
|
||||
|
||||
_dnsServer.LogManager?.Write("DNS Cache was saved to disk successfully.");
|
||||
}
|
||||
|
||||
public void DeleteCacheZoneFile()
|
||||
{
|
||||
string cacheZoneFile = Path.Combine(_dnsServer.ConfigFolder, "cache.bin");
|
||||
|
||||
if (File.Exists(cacheZoneFile))
|
||||
File.Delete(cacheZoneFile);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
@@ -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
|
||||
@@ -146,7 +146,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
while (index < history.Count)
|
||||
{
|
||||
//check difference sequence
|
||||
if (history[index].GetDeletedOn() > expiry)
|
||||
if (history[index].GetAuthRecordInfo().DeletedOn > expiry)
|
||||
break; //found record to keep
|
||||
|
||||
//skip to next difference sequence
|
||||
@@ -207,7 +207,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
//notify all secondary name servers
|
||||
foreach (DnsResourceRecord nsRecord in nsRecords)
|
||||
{
|
||||
if (nsRecord.IsDisabled())
|
||||
if (nsRecord.GetAuthRecordInfo().Disabled)
|
||||
continue;
|
||||
|
||||
string nameServerHost = (nsRecord.RDATA as DnsNSRecordData).NameServer;
|
||||
@@ -430,8 +430,8 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
string nsDomain = (nsRecord.RDATA as DnsNSRecordData).NameServer;
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = nsRecord.GetGlueRecords();
|
||||
if (glueRecords.Count > 0)
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = nsRecord.GetAuthRecordInfo().GlueRecords;
|
||||
if (glueRecords is not null)
|
||||
{
|
||||
foreach (DnsResourceRecord glueRecord in glueRecords)
|
||||
{
|
||||
@@ -514,8 +514,8 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
DnsResourceRecord soaRecord = _entries[DnsResourceRecordType.SOA][0];
|
||||
|
||||
IReadOnlyList<NameServerAddress> primaryNameServers = soaRecord.GetPrimaryNameServers();
|
||||
if (primaryNameServers.Count > 0)
|
||||
IReadOnlyList<NameServerAddress> primaryNameServers = soaRecord.GetAuthRecordInfo().PrimaryNameServers;
|
||||
if (primaryNameServers is not null)
|
||||
{
|
||||
List<NameServerAddress> resolvedNameServers = new List<NameServerAddress>(primaryNameServers.Count * 2);
|
||||
|
||||
@@ -537,7 +537,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
foreach (DnsResourceRecord nsRecord in nsRecords)
|
||||
{
|
||||
if (nsRecord.IsDisabled())
|
||||
if (nsRecord.GetAuthRecordInfo().Disabled)
|
||||
continue;
|
||||
|
||||
if (primaryNameServer.Equals((nsRecord.RDATA as DnsNSRecordData).NameServer, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -563,7 +563,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
foreach (DnsResourceRecord nsRecord in nsRecords)
|
||||
{
|
||||
if (nsRecord.IsDisabled())
|
||||
if (nsRecord.GetAuthRecordInfo().Disabled)
|
||||
continue;
|
||||
|
||||
if (primaryNameServer.Equals((nsRecord.RDATA as DnsNSRecordData).NameServer, StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
@@ -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,22 +56,30 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
if (records.Count == 1)
|
||||
{
|
||||
if (records[0].IsDisabled())
|
||||
AuthRecordInfo authRecordInfo = records[0].GetAuthRecordInfo();
|
||||
|
||||
if (authRecordInfo.Disabled)
|
||||
return Array.Empty<DnsResourceRecord>(); //record disabled
|
||||
|
||||
//update last used on
|
||||
records[0].GetRecordInfo().LastUsedOn = DateTime.UtcNow;
|
||||
authRecordInfo.LastUsedOn = DateTime.UtcNow;
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
List<DnsResourceRecord> newRecords = new List<DnsResourceRecord>(records.Count);
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
foreach (DnsResourceRecord record in records)
|
||||
{
|
||||
if (record.IsDisabled())
|
||||
AuthRecordInfo authRecordInfo = record.GetAuthRecordInfo();
|
||||
|
||||
if (authRecordInfo.Disabled)
|
||||
continue; //record disabled
|
||||
|
||||
//update last used on
|
||||
authRecordInfo.LastUsedOn = utcNow;
|
||||
|
||||
newRecords.Add(record);
|
||||
}
|
||||
|
||||
@@ -87,12 +95,6 @@ namespace DnsServerCore.Dns.Zones
|
||||
}
|
||||
}
|
||||
|
||||
//update last used on
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
foreach (DnsResourceRecord record in newRecords)
|
||||
record.GetRecordInfo().LastUsedOn = utcNow;
|
||||
|
||||
return newRecords;
|
||||
}
|
||||
|
||||
@@ -112,7 +114,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
if ((rrsigRecord.RDATA as DnsRRSIGRecordData).TypeCovered == type)
|
||||
{
|
||||
rrsigRecord.GetRecordInfo().LastUsedOn = utcNow;
|
||||
rrsigRecord.GetAuthRecordInfo().LastUsedOn = utcNow;
|
||||
newRecords.Add(rrsigRecord);
|
||||
}
|
||||
}
|
||||
@@ -491,10 +493,10 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
|
||||
|
||||
uint signatureValidityPeriod = rrsig.SignatureExpirationValue - rrsig.SignatureInceptionValue;
|
||||
uint signatureValidityPeriod = rrsig.SignatureExpiration - rrsig.SignatureInception;
|
||||
uint refreshPeriod = signatureValidityPeriod / 3;
|
||||
|
||||
if (utcNow > DateTime.UnixEpoch.AddSeconds(rrsig.SignatureExpirationValue - refreshPeriod))
|
||||
if (utcNow > DateTime.UnixEpoch.AddSeconds(rrsig.SignatureExpiration - refreshPeriod))
|
||||
typesToRefresh.Add(rrsig.TypeCovered);
|
||||
}
|
||||
|
||||
@@ -531,7 +533,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
DnsNSECRecordData newNSecRecord = new DnsNSECRecordData(nextDomainName, types);
|
||||
|
||||
if (!_entries.TryGetValue(DnsResourceRecordType.NSEC, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TtlValue != ttl) || !existingRecords[0].RDATA.Equals(newNSecRecord))
|
||||
if (!_entries.TryGetValue(DnsResourceRecordType.NSEC, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TTL != ttl) || !existingRecords[0].RDATA.Equals(newNSecRecord))
|
||||
return new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NSEC, DnsClass.IN, ttl, newNSecRecord) };
|
||||
|
||||
return Array.Empty<DnsResourceRecord>();
|
||||
@@ -539,7 +541,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
internal IReadOnlyList<DnsResourceRecord> GetUpdatedNSec3RRSet(IReadOnlyList<DnsResourceRecord> newNSec3Records)
|
||||
{
|
||||
if (!_entries.TryGetValue(DnsResourceRecordType.NSEC3, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TtlValue != newNSec3Records[0].TtlValue) || !existingRecords[0].RDATA.Equals(newNSec3Records[0].RDATA))
|
||||
if (!_entries.TryGetValue(DnsResourceRecordType.NSEC3, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TTL != newNSec3Records[0].TTL) || !existingRecords[0].RDATA.Equals(newNSec3Records[0].RDATA))
|
||||
return newNSec3Records;
|
||||
|
||||
return Array.Empty<DnsResourceRecord>();
|
||||
@@ -901,7 +903,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
foreach (DnsResourceRecord record in records)
|
||||
{
|
||||
if (record.IsDisabled())
|
||||
if (record.GetAuthRecordInfo().Disabled)
|
||||
continue;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -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
|
||||
@@ -61,9 +61,6 @@ namespace DnsServerCore.Dns.Zones
|
||||
readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, IReadOnlyList<DnsResourceRecordType>>> _updateSecurityPolicies;
|
||||
readonly IReadOnlyCollection<DnssecPrivateKey> _dnssecPrivateKeys;
|
||||
|
||||
readonly bool _notifyFailed; //not serialized
|
||||
readonly bool _syncFailed; //not serialized
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
@@ -117,7 +114,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
IPAddress[] nameServers = new IPAddress[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
nameServers[i] = IPAddressExtension.ReadFrom(bR);
|
||||
nameServers[i] = IPAddressExtensions.ReadFrom(bR);
|
||||
|
||||
_zoneTransferNameServers = nameServers;
|
||||
}
|
||||
@@ -132,7 +129,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
IPAddress[] nameServers = new IPAddress[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
nameServers[i] = IPAddressExtension.ReadFrom(bR);
|
||||
nameServers[i] = IPAddressExtensions.ReadFrom(bR);
|
||||
|
||||
_notifyNameServers = nameServers;
|
||||
}
|
||||
@@ -148,7 +145,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
IPAddress[] ipAddresses = new IPAddress[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
ipAddresses[i] = IPAddressExtension.ReadFrom(bR);
|
||||
ipAddresses[i] = IPAddressExtensions.ReadFrom(bR);
|
||||
|
||||
_updateIpAddresses = ipAddresses;
|
||||
}
|
||||
@@ -183,7 +180,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
zoneHistory[i] = new DnsResourceRecord(bR.BaseStream);
|
||||
zoneHistory[i].Tag = new DnsResourceRecordInfo(bR, zoneHistory[i].Type == DnsResourceRecordType.SOA);
|
||||
zoneHistory[i].Tag = new AuthRecordInfo(bR, zoneHistory[i].Type == DnsResourceRecordType.SOA);
|
||||
}
|
||||
|
||||
_zoneHistory = zoneHistory;
|
||||
@@ -259,7 +256,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
List<DnssecPrivateKey> dnssecPrivateKeys = new List<DnssecPrivateKey>(count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
dnssecPrivateKeys.Add(DnssecPrivateKey.Parse(bR));
|
||||
dnssecPrivateKeys.Add(DnssecPrivateKey.ReadFrom(bR));
|
||||
|
||||
_dnssecPrivateKeys = dnssecPrivateKeys;
|
||||
}
|
||||
@@ -278,7 +275,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
zoneHistory[i] = new DnsResourceRecord(bR.BaseStream);
|
||||
zoneHistory[i].Tag = new DnsResourceRecordInfo(bR, zoneHistory[i].Type == DnsResourceRecordType.SOA);
|
||||
zoneHistory[i].Tag = new AuthRecordInfo(bR, zoneHistory[i].Type == DnsResourceRecordType.SOA);
|
||||
}
|
||||
|
||||
_zoneHistory = zoneHistory;
|
||||
@@ -334,8 +331,6 @@ namespace DnsServerCore.Dns.Zones
|
||||
_zoneTransferTsigKeyNames = primaryZone.ZoneTransferTsigKeyNames;
|
||||
_updateSecurityPolicies = primaryZone.UpdateSecurityPolicies;
|
||||
_dnssecPrivateKeys = primaryZone.DnssecPrivateKeys;
|
||||
|
||||
_notifyFailed = primaryZone.NotifyFailed;
|
||||
}
|
||||
else if (_apexZone is SecondaryZone secondaryZone)
|
||||
{
|
||||
@@ -346,16 +341,11 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
_expiry = secondaryZone.Expiry;
|
||||
_zoneTransferTsigKeyNames = secondaryZone.ZoneTransferTsigKeyNames;
|
||||
|
||||
_notifyFailed = secondaryZone.NotifyFailed;
|
||||
_syncFailed = secondaryZone.SyncFailed;
|
||||
}
|
||||
else if (_apexZone is StubZone stubZone)
|
||||
{
|
||||
_type = AuthZoneType.Stub;
|
||||
_expiry = stubZone.Expiry;
|
||||
|
||||
_syncFailed = stubZone.SyncFailed;
|
||||
}
|
||||
else if (_apexZone is ForwarderZone)
|
||||
{
|
||||
@@ -527,8 +517,8 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
record.WriteTo(bW.BaseStream);
|
||||
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
rrInfo = new DnsResourceRecordInfo(); //default info
|
||||
if (record.Tag is not AuthRecordInfo rrInfo)
|
||||
rrInfo = AuthRecordInfo.Default; //default info
|
||||
|
||||
rrInfo.WriteTo(bW);
|
||||
}
|
||||
@@ -598,8 +588,8 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
record.WriteTo(bW.BaseStream);
|
||||
|
||||
if (record.Tag is not DnsResourceRecordInfo rrInfo)
|
||||
rrInfo = new DnsResourceRecordInfo(); //default info
|
||||
if (record.Tag is not AuthRecordInfo rrInfo)
|
||||
rrInfo = AuthRecordInfo.Default; //default info
|
||||
|
||||
rrInfo.WriteTo(bW);
|
||||
}
|
||||
@@ -630,6 +620,22 @@ namespace DnsServerCore.Dns.Zones
|
||||
return _name.CompareTo(other._name);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
|
||||
if (obj is not AuthZoneInfo other)
|
||||
return false;
|
||||
|
||||
return _name.Equals(other._name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _name.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _name;
|
||||
@@ -650,7 +656,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
public bool Disabled
|
||||
{
|
||||
get { return _disabled; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _disabled;
|
||||
|
||||
return _apexZone.Disabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -662,7 +674,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
public AuthZoneTransfer ZoneTransfer
|
||||
{
|
||||
get { return _zoneTransfer; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _zoneTransfer;
|
||||
|
||||
return _apexZone.ZoneTransfer;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -674,7 +692,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
public IReadOnlyCollection<IPAddress> ZoneTransferNameServers
|
||||
{
|
||||
get { return _zoneTransferNameServers; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _zoneTransferNameServers;
|
||||
|
||||
return _apexZone.ZoneTransferNameServers;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -686,7 +710,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
public AuthZoneNotify Notify
|
||||
{
|
||||
get { return _notify; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _notify;
|
||||
|
||||
return _apexZone.Notify;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -698,7 +728,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
public IReadOnlyCollection<IPAddress> NotifyNameServers
|
||||
{
|
||||
get { return _notifyNameServers; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _notifyNameServers;
|
||||
|
||||
return _apexZone.NotifyNameServers;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -710,7 +746,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
public AuthZoneUpdate Update
|
||||
{
|
||||
get { return _update; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _update;
|
||||
|
||||
return _apexZone.Update;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -722,7 +764,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
public IReadOnlyCollection<IPAddress> UpdateIpAddresses
|
||||
{
|
||||
get { return _updateIpAddresses; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _updateIpAddresses;
|
||||
|
||||
return _apexZone.UpdateIpAddresses;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -733,53 +781,46 @@ namespace DnsServerCore.Dns.Zones
|
||||
}
|
||||
|
||||
public DateTime Expiry
|
||||
{ get { return _expiry; } }
|
||||
|
||||
public bool IsExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
throw new InvalidOperationException();
|
||||
return _expiry;
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case AuthZoneType.Secondary:
|
||||
return (_apexZone as SecondaryZone).IsExpired;
|
||||
return (_apexZone as SecondaryZone).Expiry;
|
||||
|
||||
case AuthZoneType.Stub:
|
||||
return (_apexZone as StubZone).IsExpired;
|
||||
return (_apexZone as StubZone).Expiry;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Internal
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
return (_apexZone as PrimaryZone).Internal;
|
||||
|
||||
default:
|
||||
return false;
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> ZoneHistory
|
||||
{ get { return _zoneHistory; } }
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _zoneHistory;
|
||||
|
||||
return _apexZone.GetZoneHistory();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, object> ZoneTransferTsigKeyNames
|
||||
{
|
||||
get { return _zoneTransferTsigKeyNames; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _zoneTransferTsigKeyNames;
|
||||
|
||||
return _apexZone.ZoneTransferTsigKeyNames;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -800,7 +841,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, IReadOnlyList<DnsResourceRecordType>>> UpdateSecurityPolicies
|
||||
{
|
||||
get { return _updateSecurityPolicies; }
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _updateSecurityPolicies;
|
||||
|
||||
return _apexZone.UpdateSecurityPolicies;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_apexZone is null)
|
||||
@@ -818,6 +865,24 @@ namespace DnsServerCore.Dns.Zones
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<DnssecPrivateKey> DnssecPrivateKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
return _dnssecPrivateKeys;
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
return (_apexZone as PrimaryZone).DnssecPrivateKeys;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AuthZoneDnssecStatus DnssecStatus
|
||||
{
|
||||
get
|
||||
@@ -842,19 +907,91 @@ namespace DnsServerCore.Dns.Zones
|
||||
return (_apexZone as PrimaryZone).GetDnsKeyTtl();
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<DnssecPrivateKey> DnssecPrivateKeys
|
||||
{ get { return _dnssecPrivateKeys; } }
|
||||
public bool Internal
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
return (_apexZone as PrimaryZone).Internal;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case AuthZoneType.Secondary:
|
||||
return (_apexZone as SecondaryZone).IsExpired;
|
||||
|
||||
case AuthZoneType.Stub:
|
||||
return (_apexZone as StubZone).IsExpired;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool NotifyFailed
|
||||
{ get { return _notifyFailed; } }
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
return (_apexZone as PrimaryZone).NotifyFailed;
|
||||
|
||||
case AuthZoneType.Secondary:
|
||||
return (_apexZone as SecondaryZone).NotifyFailed;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SyncFailed
|
||||
{ get { return _syncFailed; } }
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apexZone is null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case AuthZoneType.Secondary:
|
||||
return (_apexZone as SecondaryZone).SyncFailed;
|
||||
|
||||
case AuthZoneType.Stub:
|
||||
return (_apexZone as StubZone).SyncFailed;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -21,6 +21,7 @@ using DnsServerCore.Dns.ResourceRecords;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using TechnitiumLibrary;
|
||||
using TechnitiumLibrary.Net;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
@@ -42,6 +43,67 @@ namespace DnsServerCore.Dns.Zones
|
||||
: base(name, capacity)
|
||||
{ }
|
||||
|
||||
private CacheZone(string name, ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entries)
|
||||
: base(name, entries)
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
|
||||
#region static
|
||||
|
||||
public static CacheZone ReadFrom(BinaryReader bR, bool serveStale)
|
||||
{
|
||||
byte version = bR.ReadByte();
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
string name = bR.ReadString();
|
||||
ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entries = ReadEntriesFrom(bR, serveStale);
|
||||
|
||||
CacheZone cacheZone = new CacheZone(name, entries);
|
||||
|
||||
//write all ECS cache records
|
||||
{
|
||||
int ecsCount = bR.ReadInt32();
|
||||
if (ecsCount > 0)
|
||||
{
|
||||
ConcurrentDictionary<NetworkAddress, ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>>> ecsEntries = new ConcurrentDictionary<NetworkAddress, ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>>>(1, ecsCount);
|
||||
|
||||
for (int i = 0; i < ecsCount; i++)
|
||||
{
|
||||
NetworkAddress key = NetworkAddress.ReadFrom(bR);
|
||||
ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> ecsEntry = ReadEntriesFrom(bR, serveStale);
|
||||
|
||||
if (!ecsEntry.IsEmpty)
|
||||
ecsEntries.TryAdd(key, ecsEntry);
|
||||
}
|
||||
|
||||
if (!ecsEntries.IsEmpty)
|
||||
cacheZone._ecsEntries = ecsEntries;
|
||||
}
|
||||
}
|
||||
|
||||
return cacheZone;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("CacheZone format version not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsTypeSupportedForEDnsClientSubnet(DnsResourceRecordType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DnsResourceRecordType.A:
|
||||
case DnsResourceRecordType.AAAA:
|
||||
case DnsResourceRecordType.CNAME:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
@@ -73,22 +135,56 @@ namespace DnsServerCore.Dns.Zones
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
foreach (DnsResourceRecord record in records)
|
||||
record.GetRecordInfo().LastUsedOn = utcNow;
|
||||
record.GetCacheRecordInfo().LastUsedOn = utcNow;
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
public static bool IsTypeSupportedForEDnsClientSubnet(DnsResourceRecordType type)
|
||||
private static ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> ReadEntriesFrom(BinaryReader bR, bool serveStale)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DnsResourceRecordType.A:
|
||||
case DnsResourceRecordType.AAAA:
|
||||
case DnsResourceRecordType.CNAME:
|
||||
return true;
|
||||
int count = bR.ReadInt32();
|
||||
ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entries = new ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>>(1, count);
|
||||
|
||||
default:
|
||||
return false;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
DnsResourceRecordType key = (DnsResourceRecordType)bR.ReadUInt16();
|
||||
int rrCount = bR.ReadInt32();
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[rrCount];
|
||||
|
||||
for (int j = 0; j < rrCount; j++)
|
||||
{
|
||||
records[j] = DnsResourceRecord.ReadCacheRecordFrom(bR, delegate (DnsResourceRecord record)
|
||||
{
|
||||
record.Tag = new CacheRecordInfo(bR);
|
||||
});
|
||||
}
|
||||
|
||||
if (!DnsResourceRecord.IsRRSetExpired(records, serveStale))
|
||||
entries.TryAdd(key, records);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static void WriteEntriesTo(ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entries, BinaryWriter bW)
|
||||
{
|
||||
bW.Write(entries.Count);
|
||||
|
||||
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in entries)
|
||||
{
|
||||
bW.Write((ushort)entry.Key);
|
||||
bW.Write(entry.Value.Count);
|
||||
|
||||
foreach (DnsResourceRecord record in entry.Value)
|
||||
{
|
||||
record.WriteCacheRecordTo(bW, delegate ()
|
||||
{
|
||||
if (record.Tag is not CacheRecordInfo rrInfo)
|
||||
rrInfo = CacheRecordInfo.Default; //default info
|
||||
|
||||
rrInfo.WriteTo(bW);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +199,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entries;
|
||||
|
||||
NetworkAddress eDnsClientSubnet = records[0].GetRecordInfo().EDnsClientSubnet;
|
||||
NetworkAddress eDnsClientSubnet = records[0].GetCacheRecordInfo().EDnsClientSubnet;
|
||||
if ((eDnsClientSubnet is null) || !IsTypeSupportedForEDnsClientSubnet(type))
|
||||
{
|
||||
entries = _entries;
|
||||
@@ -163,7 +259,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
foreach (DnsResourceRecord record in records)
|
||||
record.GetRecordInfo().LastUsedOn = utcNow;
|
||||
record.GetCacheRecordInfo().LastUsedOn = utcNow;
|
||||
|
||||
//set records
|
||||
bool added = true;
|
||||
@@ -223,7 +319,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
}
|
||||
}
|
||||
|
||||
if (ecsEntry.Value.Count == 0)
|
||||
if (ecsEntry.Value.IsEmpty)
|
||||
_ecsEntries.TryRemove(ecsEntry.Key, out _);
|
||||
}
|
||||
}
|
||||
@@ -250,21 +346,21 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in ecsEntry.Value)
|
||||
{
|
||||
if ((entry.Value.Count == 0) || (entry.Value[0].GetRecordInfo().LastUsedOn < cutoff))
|
||||
if ((entry.Value.Count == 0) || (entry.Value[0].GetCacheRecordInfo().LastUsedOn < cutoff))
|
||||
{
|
||||
if (ecsEntry.Value.TryRemove(entry.Key, out _)) //RR Set was last used before cutoff; remove entry
|
||||
removedEntries++;
|
||||
}
|
||||
}
|
||||
|
||||
if (ecsEntry.Value.Count == 0)
|
||||
if (ecsEntry.Value.IsEmpty)
|
||||
_ecsEntries.TryRemove(ecsEntry.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
|
||||
{
|
||||
if ((entry.Value.Count == 0) || (entry.Value[0].GetRecordInfo().LastUsedOn < cutoff))
|
||||
if ((entry.Value.Count == 0) || (entry.Value[0].GetCacheRecordInfo().LastUsedOn < cutoff))
|
||||
{
|
||||
if (_entries.TryRemove(entry.Key, out _)) //RR Set was last used before cutoff; remove entry
|
||||
removedEntries++;
|
||||
@@ -420,6 +516,33 @@ namespace DnsServerCore.Dns.Zones
|
||||
return false;
|
||||
}
|
||||
|
||||
public void WriteTo(BinaryWriter bW)
|
||||
{
|
||||
bW.Write((byte)1); //version
|
||||
|
||||
//cache zone info
|
||||
bW.Write(_name);
|
||||
|
||||
//write all cache records
|
||||
WriteEntriesTo(_entries, bW);
|
||||
|
||||
//write all ECS cache records
|
||||
if (_ecsEntries is null)
|
||||
{
|
||||
bW.Write(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
bW.Write(_ecsEntries.Count);
|
||||
|
||||
foreach (KeyValuePair<NetworkAddress, ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>>> ecsEntry in _ecsEntries)
|
||||
{
|
||||
ecsEntry.Key.WriteTo(bW);
|
||||
WriteEntriesTo(ecsEntry.Value, bW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
@@ -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
|
||||
@@ -44,7 +44,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
DnsResourceRecord fwdRecord = new DnsResourceRecord(name, DnsResourceRecordType.FWD, DnsClass.IN, 0, new DnsForwarderRecordData(forwarderProtocol, forwarder, dnssecValidation, proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword));
|
||||
|
||||
if (!string.IsNullOrEmpty(fwdRecordComments))
|
||||
fwdRecord.SetComments(fwdRecordComments);
|
||||
fwdRecord.GetAuthRecordInfo().Comments = fwdRecordComments;
|
||||
|
||||
_entries[DnsResourceRecordType.FWD] = new DnsResourceRecord[] { fwdRecord };
|
||||
}
|
||||
|
||||
@@ -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,7 +66,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
default:
|
||||
foreach (DnsResourceRecord record in records)
|
||||
{
|
||||
if (record.IsDisabled())
|
||||
if (record.GetAuthRecordInfo().Disabled)
|
||||
throw new DnsServerException("Cannot set records: disabling records in a signed zones is not supported.");
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
throw new DnsServerException("The record type is not supported by DNSSEC signed primary zones.");
|
||||
|
||||
default:
|
||||
if (record.IsDisabled())
|
||||
if (record.GetAuthRecordInfo().Disabled)
|
||||
throw new DnsServerException("Cannot add record: disabling records in a signed zones is not supported.");
|
||||
|
||||
break;
|
||||
@@ -226,7 +226,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
if (oldRecord.Type != newRecord.Type)
|
||||
throw new InvalidOperationException("Old and new record types do not match.");
|
||||
|
||||
if ((_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned) && newRecord.IsDisabled())
|
||||
if ((_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned) && newRecord.GetAuthRecordInfo().Disabled)
|
||||
throw new DnsServerException("Cannot update record: disabling records in a signed zones is not supported.");
|
||||
|
||||
if (newRecord.OriginalTtlValue > _primaryZone.GetZoneSoaExpire())
|
||||
|
||||
@@ -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
|
||||
@@ -826,7 +826,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
IReadOnlyList<DnsResourceRecord> nsec3ParamRecords = GetRecords(DnsResourceRecordType.NSEC3PARAM);
|
||||
DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecords[0].RDATA as DnsNSEC3PARAMRecordData;
|
||||
|
||||
EnableNSec3(nonNSec3Zones, nsec3Param.Iterations, nsec3Param.SaltValue);
|
||||
EnableNSec3(nonNSec3Zones, nsec3Param.Iterations, nsec3Param.Salt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2195,7 +2195,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
if (nextHashedOwnerName is null)
|
||||
nextHashedOwnerName = DnsNSEC3RecordData.GetHashedOwnerNameFrom(hashedOwnerName); //only 1 NSEC3 record in zone
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> newNSec3Records = zone.CreateNSec3RRSet(hashedOwnerName, nextHashedOwnerName, ttl, nsec3Param.Iterations, nsec3Param.SaltValue);
|
||||
IReadOnlyList<DnsResourceRecord> newNSec3Records = zone.CreateNSec3RRSet(hashedOwnerName, nextHashedOwnerName, ttl, nsec3Param.Iterations, nsec3Param.Salt);
|
||||
|
||||
if (forceGetNewRRSet)
|
||||
return newNSec3Records;
|
||||
@@ -2310,9 +2310,9 @@ namespace DnsServerCore.Dns.Zones
|
||||
DnsNSEC3RecordData newPreviousNSec3;
|
||||
|
||||
if (wasRemoved)
|
||||
newPreviousNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, previousNSec3.Iterations, previousNSec3.SaltValue, currentNSec3.NextHashedOwnerNameValue, previousNSec3.Types);
|
||||
newPreviousNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, previousNSec3.Iterations, previousNSec3.Salt, currentNSec3.NextHashedOwnerNameValue, previousNSec3.Types);
|
||||
else
|
||||
newPreviousNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, previousNSec3.Iterations, previousNSec3.SaltValue, DnsNSEC3RecordData.GetHashedOwnerNameFrom(currentNSec3Record.Name), previousNSec3.Types);
|
||||
newPreviousNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, previousNSec3.Iterations, previousNSec3.Salt, DnsNSEC3RecordData.GetHashedOwnerNameFrom(currentNSec3Record.Name), previousNSec3.Types);
|
||||
|
||||
DnsResourceRecord[] newPreviousNSec3Records = new DnsResourceRecord[] { new DnsResourceRecord(previousNSec3Record.Name, DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, newPreviousNSec3) };
|
||||
|
||||
@@ -2611,7 +2611,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
else
|
||||
serial = 1;
|
||||
|
||||
newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, addSoaRecord.TtlValue, new DnsSOARecordData(addSoa.PrimaryNameServer, addSoa.ResponsiblePerson, serial, addSoa.Refresh, addSoa.Retry, addSoa.Expire, addSoa.Minimum)) { Tag = addSoaRecord.Tag };
|
||||
newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, addSoaRecord.TTL, new DnsSOARecordData(addSoa.PrimaryNameServer, addSoa.ResponsiblePerson, serial, addSoa.Refresh, addSoa.Retry, addSoa.Expire, addSoa.Minimum)) { Tag = addSoaRecord.Tag };
|
||||
addedRecords = null;
|
||||
}
|
||||
else
|
||||
@@ -2623,7 +2623,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
else
|
||||
serial = 1;
|
||||
|
||||
newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, oldSoaRecord.TtlValue, new DnsSOARecordData(oldSoa.PrimaryNameServer, oldSoa.ResponsiblePerson, serial, oldSoa.Refresh, oldSoa.Retry, oldSoa.Expire, oldSoa.Minimum)) { Tag = oldSoaRecord.Tag };
|
||||
newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, oldSoaRecord.TTL, new DnsSOARecordData(oldSoa.PrimaryNameServer, oldSoa.ResponsiblePerson, serial, oldSoa.Refresh, oldSoa.Retry, oldSoa.Expire, oldSoa.Minimum)) { Tag = oldSoaRecord.Tag };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2646,7 +2646,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
oldSoaRecord.Tag = null;
|
||||
|
||||
//start commit
|
||||
oldSoaRecord.SetDeletedOn(DateTime.UtcNow);
|
||||
oldSoaRecord.GetAuthRecordInfo().DeletedOn = DateTime.UtcNow;
|
||||
|
||||
//write removed
|
||||
_zoneHistory.Add(oldSoaRecord);
|
||||
@@ -2655,13 +2655,17 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
foreach (DnsResourceRecord deletedRecord in deletedRecords)
|
||||
{
|
||||
if (deletedRecord.IsDisabled())
|
||||
if (deletedRecord.GetAuthRecordInfo().Disabled)
|
||||
continue;
|
||||
|
||||
_zoneHistory.Add(deletedRecord);
|
||||
|
||||
if (deletedRecord.Type == DnsResourceRecordType.NS)
|
||||
_zoneHistory.AddRange(deletedRecord.GetGlueRecords());
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = deletedRecord.GetAuthRecordInfo().GlueRecords;
|
||||
if (glueRecords is not null)
|
||||
_zoneHistory.AddRange(glueRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2675,13 +2679,17 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
foreach (DnsResourceRecord addedRecord in addedRecords)
|
||||
{
|
||||
if (addedRecord.IsDisabled())
|
||||
if (addedRecord.GetAuthRecordInfo().Disabled)
|
||||
continue;
|
||||
|
||||
_zoneHistory.Add(addedRecord);
|
||||
|
||||
if (addedRecord.Type == DnsResourceRecordType.NS)
|
||||
_zoneHistory.AddRange(addedRecord.GetGlueRecords());
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = addedRecord.GetAuthRecordInfo().GlueRecords;
|
||||
if (glueRecords is not null)
|
||||
_zoneHistory.AddRange(glueRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2711,7 +2719,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
default:
|
||||
foreach (DnsResourceRecord record in records)
|
||||
{
|
||||
if (record.IsDisabled())
|
||||
if (record.GetAuthRecordInfo().Disabled)
|
||||
throw new DnsServerException("Cannot set records: disabling records in a signed zones is not supported.");
|
||||
}
|
||||
|
||||
@@ -2741,10 +2749,10 @@ namespace DnsServerCore.Dns.Zones
|
||||
if (newSoa.Refresh > newSoa.Expire)
|
||||
throw new DnsServerException("Failed to set records: SOA REFRESH cannot be greater than SOA EXPIRE.");
|
||||
|
||||
//remove any resource record info except comments
|
||||
string comments = newSoaRecord.GetComments();
|
||||
newSoaRecord.Tag = null;
|
||||
newSoaRecord.SetComments(comments);
|
||||
//remove any record info except comments
|
||||
string comments = newSoaRecord.GetAuthRecordInfo().Comments;
|
||||
newSoaRecord.Tag = null; //remove old record info
|
||||
newSoaRecord.GetAuthRecordInfo().Comments = comments;
|
||||
|
||||
uint oldSoaMinimum = GetZoneSoaMinimum();
|
||||
|
||||
@@ -2806,7 +2814,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
throw new DnsServerException("The record type is not supported by DNSSEC signed primary zones.");
|
||||
|
||||
default:
|
||||
if (record.IsDisabled())
|
||||
if (record.GetAuthRecordInfo().Disabled)
|
||||
throw new DnsServerException("Cannot add record: disabling records in a signed zones is not supported.");
|
||||
|
||||
break;
|
||||
@@ -2930,7 +2938,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
if (oldRecord.Type != newRecord.Type)
|
||||
throw new InvalidOperationException("Old and new record types do not match.");
|
||||
|
||||
if ((_dnssecStatus != AuthZoneDnssecStatus.Unsigned) && newRecord.IsDisabled())
|
||||
if ((_dnssecStatus != AuthZoneDnssecStatus.Unsigned) && newRecord.GetAuthRecordInfo().Disabled)
|
||||
throw new DnsServerException("Cannot update record: disabling records in a signed zones is not supported.");
|
||||
|
||||
if (newRecord.OriginalTtlValue > GetZoneSoaExpire())
|
||||
|
||||
@@ -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
|
||||
@@ -22,6 +22,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TechnitiumLibrary;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
@@ -88,6 +89,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
case DnsTransportProtocol.Tcp:
|
||||
case DnsTransportProtocol.Tls:
|
||||
case DnsTransportProtocol.Quic:
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -98,14 +100,25 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
DnsQuestionRecord soaQuestion = new DnsQuestionRecord(name, DnsResourceRecordType.SOA, DnsClass.IN);
|
||||
DnsDatagram soaResponse;
|
||||
NameServerAddress[] primaryNameServers = null;
|
||||
|
||||
if (primaryNameServerAddresses == null)
|
||||
if (string.IsNullOrEmpty(primaryNameServerAddresses))
|
||||
{
|
||||
soaResponse = await secondaryZone._dnsServer.DirectQueryAsync(soaQuestion);
|
||||
}
|
||||
else
|
||||
{
|
||||
DnsClient dnsClient = new DnsClient(primaryNameServerAddresses);
|
||||
primaryNameServers = primaryNameServerAddresses.Split(delegate (string address)
|
||||
{
|
||||
NameServerAddress nameServer = NameServerAddress.Parse(address);
|
||||
|
||||
if (nameServer.Protocol != zoneTransferProtocol)
|
||||
nameServer = nameServer.ChangeProtocol(zoneTransferProtocol);
|
||||
|
||||
return nameServer;
|
||||
}, ',');
|
||||
|
||||
DnsClient dnsClient = new DnsClient(primaryNameServers);
|
||||
|
||||
foreach (NameServerAddress nameServerAddress in dnsClient.Servers)
|
||||
{
|
||||
@@ -134,13 +147,11 @@ namespace DnsServerCore.Dns.Zones
|
||||
DnsSOARecordData soa = new DnsSOARecordData(receivedSoa.PrimaryNameServer, receivedSoa.ResponsiblePerson, 0u, receivedSoa.Refresh, receivedSoa.Retry, receivedSoa.Expire, receivedSoa.Minimum);
|
||||
DnsResourceRecord[] soaRR = new DnsResourceRecord[] { new DnsResourceRecord(secondaryZone._name, DnsResourceRecordType.SOA, DnsClass.IN, soa.Refresh, soa) };
|
||||
|
||||
if (!string.IsNullOrEmpty(primaryNameServerAddresses))
|
||||
soaRR[0].SetPrimaryNameServers(primaryNameServerAddresses);
|
||||
AuthRecordInfo authRecordInfo = soaRR[0].GetAuthRecordInfo();
|
||||
|
||||
DnsResourceRecordInfo recordInfo = soaRR[0].GetRecordInfo();
|
||||
|
||||
recordInfo.ZoneTransferProtocol = zoneTransferProtocol;
|
||||
recordInfo.TsigKeyName = tsigKeyName;
|
||||
authRecordInfo.PrimaryNameServers = primaryNameServers;
|
||||
authRecordInfo.ZoneTransferProtocol = zoneTransferProtocol;
|
||||
authRecordInfo.TsigKeyName = tsigKeyName;
|
||||
|
||||
secondaryZone._entries[DnsResourceRecordType.SOA] = soaRR;
|
||||
|
||||
@@ -214,7 +225,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
return;
|
||||
}
|
||||
|
||||
DnsResourceRecordInfo recordInfo = currentSoaRecord.GetRecordInfo();
|
||||
AuthRecordInfo recordInfo = currentSoaRecord.GetAuthRecordInfo();
|
||||
TsigKey key = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(recordInfo.TsigKeyName) && ((_dnsServer.TsigKeys is null) || !_dnsServer.TsigKeys.TryGetValue(recordInfo.TsigKeyName, out key)))
|
||||
@@ -289,7 +300,18 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
if (!_resync)
|
||||
{
|
||||
DnsClient client = new DnsClient(primaryNameServers);
|
||||
//check for update; use UDP transport
|
||||
List<NameServerAddress> udpNameServers = new List<NameServerAddress>(primaryNameServers.Count);
|
||||
|
||||
foreach (NameServerAddress primaryNameServer in primaryNameServers)
|
||||
{
|
||||
if (primaryNameServer.Protocol == DnsTransportProtocol.Udp)
|
||||
udpNameServers.Add(primaryNameServer);
|
||||
else
|
||||
udpNameServers.Add(primaryNameServer.ChangeProtocol(DnsTransportProtocol.Udp));
|
||||
}
|
||||
|
||||
DnsClient client = new DnsClient(udpNameServers);
|
||||
|
||||
client.Proxy = _dnsServer.Proxy;
|
||||
client.PreferIPv6 = _dnsServer.PreferIPv6;
|
||||
@@ -309,7 +331,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received RCODE=" + soaResponse.RCODE.ToString() + " for '" + (_name == "" ? "<root>" : _name) + "' secondary zone refresh from: " + soaResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received RCODE=" + soaResponse.RCODE.ToString() + " for '" + (_name == "" ? "<root>" : _name) + "' secondary zone refresh from: " + soaResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -318,7 +340,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received an empty response for SOA query for '" + (_name == "" ? "<root>" : _name) + "' secondary zone refresh from: " + soaResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received an empty response for SOA query for '" + (_name == "" ? "<root>" : _name) + "' secondary zone refresh from: " + soaResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -331,46 +353,44 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server successfully checked for '" + (_name == "" ? "<root>" : _name) + "' secondary zone update from: " + soaResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server successfully checked for '" + (_name == "" ? "<root>" : _name) + "' secondary zone update from: " + soaResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//update available; do zone transfer with TLS or TCP transport
|
||||
//update available; do zone transfer with TLS, QUIC, or TCP transport
|
||||
List<NameServerAddress> updatedNameServers = new List<NameServerAddress>(primaryNameServers.Count);
|
||||
|
||||
if (zoneTransferProtocol == DnsTransportProtocol.Tls)
|
||||
switch (zoneTransferProtocol)
|
||||
{
|
||||
//change name server protocol to TLS
|
||||
List<NameServerAddress> tlsNameServers = new List<NameServerAddress>(primaryNameServers.Count);
|
||||
case DnsTransportProtocol.Tls:
|
||||
case DnsTransportProtocol.Quic:
|
||||
//change name server protocol to TLS/QUIC
|
||||
foreach (NameServerAddress primaryNameServer in primaryNameServers)
|
||||
{
|
||||
if (primaryNameServer.Protocol == zoneTransferProtocol)
|
||||
updatedNameServers.Add(primaryNameServer);
|
||||
else
|
||||
updatedNameServers.Add(primaryNameServer.ChangeProtocol(zoneTransferProtocol));
|
||||
}
|
||||
|
||||
foreach (NameServerAddress primaryNameServer in primaryNameServers)
|
||||
{
|
||||
if (primaryNameServer.Protocol == DnsTransportProtocol.Tls)
|
||||
tlsNameServers.Add(primaryNameServer);
|
||||
else
|
||||
tlsNameServers.Add(primaryNameServer.ChangeProtocol(DnsTransportProtocol.Tls));
|
||||
}
|
||||
break;
|
||||
|
||||
primaryNameServers = tlsNameServers;
|
||||
}
|
||||
else
|
||||
{
|
||||
//change name server protocol to TCP
|
||||
List<NameServerAddress> tcpNameServers = new List<NameServerAddress>(primaryNameServers.Count);
|
||||
default:
|
||||
//change name server protocol to TCP
|
||||
foreach (NameServerAddress primaryNameServer in primaryNameServers)
|
||||
{
|
||||
if (primaryNameServer.Protocol == DnsTransportProtocol.Tcp)
|
||||
updatedNameServers.Add(primaryNameServer);
|
||||
else
|
||||
updatedNameServers.Add(primaryNameServer.ChangeProtocol(DnsTransportProtocol.Tcp));
|
||||
}
|
||||
|
||||
foreach (NameServerAddress primaryNameServer in primaryNameServers)
|
||||
{
|
||||
if (primaryNameServer.Protocol == DnsTransportProtocol.Tcp)
|
||||
tcpNameServers.Add(primaryNameServer);
|
||||
else
|
||||
tcpNameServers.Add(primaryNameServer.ChangeProtocol(DnsTransportProtocol.Tcp));
|
||||
}
|
||||
|
||||
primaryNameServers = tcpNameServers;
|
||||
break;
|
||||
}
|
||||
|
||||
DnsClient xfrClient = new DnsClient(primaryNameServers);
|
||||
DnsClient xfrClient = new DnsClient(updatedNameServers);
|
||||
|
||||
xfrClient.Proxy = _dnsServer.Proxy;
|
||||
xfrClient.PreferIPv6 = _dnsServer.PreferIPv6;
|
||||
@@ -414,7 +434,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received a zone transfer response (RCODE=" + xfrResponse.RCODE.ToString() + ") for '" + (_name == "" ? "<root>" : _name) + "' secondary zone from: " + xfrResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received a zone transfer response (RCODE=" + xfrResponse.RCODE.ToString() + ") for '" + (_name == "" ? "<root>" : _name) + "' secondary zone from: " + xfrResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -423,7 +443,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received an empty response for zone transfer query for '" + (_name == "" ? "<root>" : _name) + "' secondary zone from: " + xfrResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received an empty response for zone transfer query for '" + (_name == "" ? "<root>" : _name) + "' secondary zone from: " + xfrResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -432,7 +452,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received invalid response for zone transfer query for '" + (_name == "" ? "<root>" : _name) + "' secondary zone from: " + xfrResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received invalid response for zone transfer query for '" + (_name == "" ? "<root>" : _name) + "' secondary zone from: " + xfrResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -460,13 +480,13 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server successfully refreshed '" + (_name == "" ? "<root>" : _name) + "' secondary zone from: " + xfrResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server successfully refreshed '" + (_name == "" ? "<root>" : _name) + "' secondary zone from: " + xfrResponse.Metadata.NameServer.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server successfully checked for '" + (_name == "" ? "<root>" : _name) + "' secondary zone update from: " + xfrResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server successfully checked for '" + (_name == "" ? "<root>" : _name) + "' secondary zone update from: " + xfrResponse.Metadata.NameServer.ToString());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -499,7 +519,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
lock (_zoneHistory)
|
||||
{
|
||||
historyRecords[0].SetDeletedOn(DateTime.UtcNow);
|
||||
historyRecords[0].GetAuthRecordInfo().DeletedOn = DateTime.UtcNow;
|
||||
|
||||
//write history
|
||||
_zoneHistory.AddRange(historyRecords);
|
||||
|
||||
@@ -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
|
||||
@@ -22,6 +22,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TechnitiumLibrary;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
@@ -81,14 +82,25 @@ namespace DnsServerCore.Dns.Zones
|
||||
|
||||
DnsQuestionRecord soaQuestion = new DnsQuestionRecord(name, DnsResourceRecordType.SOA, DnsClass.IN);
|
||||
DnsDatagram soaResponse;
|
||||
NameServerAddress[] primaryNameServers = null;
|
||||
|
||||
if (primaryNameServerAddresses == null)
|
||||
if (string.IsNullOrEmpty(primaryNameServerAddresses))
|
||||
{
|
||||
soaResponse = await stubZone._dnsServer.DirectQueryAsync(soaQuestion);
|
||||
}
|
||||
else
|
||||
{
|
||||
DnsClient dnsClient = new DnsClient(primaryNameServerAddresses);
|
||||
primaryNameServers = primaryNameServerAddresses.Split(delegate (string address)
|
||||
{
|
||||
NameServerAddress nameServer = NameServerAddress.Parse(address);
|
||||
|
||||
if (nameServer.Protocol != DnsTransportProtocol.Udp)
|
||||
nameServer = nameServer.ChangeProtocol(DnsTransportProtocol.Udp);
|
||||
|
||||
return nameServer;
|
||||
}, ',');
|
||||
|
||||
DnsClient dnsClient = new DnsClient(primaryNameServers);
|
||||
|
||||
foreach (NameServerAddress nameServerAddress in dnsClient.Servers)
|
||||
{
|
||||
@@ -112,8 +124,8 @@ namespace DnsServerCore.Dns.Zones
|
||||
DnsSOARecordData soa = new DnsSOARecordData(receivedSoa.PrimaryNameServer, receivedSoa.ResponsiblePerson, 0u, receivedSoa.Refresh, receivedSoa.Retry, receivedSoa.Expire, receivedSoa.Minimum);
|
||||
DnsResourceRecord[] soaRR = new DnsResourceRecord[] { new DnsResourceRecord(stubZone._name, DnsResourceRecordType.SOA, DnsClass.IN, soa.Refresh, soa) };
|
||||
|
||||
if (!string.IsNullOrEmpty(primaryNameServerAddresses))
|
||||
soaRR[0].SetPrimaryNameServers(primaryNameServerAddresses);
|
||||
if (primaryNameServers is not null)
|
||||
soaRR[0].GetAuthRecordInfo().PrimaryNameServers = primaryNameServers;
|
||||
|
||||
stubZone._entries[DnsResourceRecordType.SOA] = soaRR;
|
||||
|
||||
@@ -255,7 +267,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received RCODE=" + soaResponse.RCODE.ToString() + " for '" + (_name == "" ? "<root>" : _name) + "' stub zone refresh from: " + soaResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received RCODE=" + soaResponse.RCODE.ToString() + " for '" + (_name == "" ? "<root>" : _name) + "' stub zone refresh from: " + soaResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -264,7 +276,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received an empty response for SOA query for '" + (_name == "" ? "<root>" : _name) + "' stub zone refresh from: " + soaResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received an empty response for SOA query for '" + (_name == "" ? "<root>" : _name) + "' stub zone refresh from: " + soaResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -280,7 +292,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server successfully checked for '" + (_name == "" ? "<root>" : _name) + "' stub zone update from: " + soaResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server successfully checked for '" + (_name == "" ? "<root>" : _name) + "' stub zone update from: " + soaResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -291,8 +303,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
foreach (NameServerAddress nameServer in nameServers)
|
||||
tcpNameServers.Add(nameServer.ChangeProtocol(DnsTransportProtocol.Tcp));
|
||||
|
||||
nameServers = tcpNameServers;
|
||||
client = new DnsClient(nameServers);
|
||||
client = new DnsClient(tcpNameServers);
|
||||
|
||||
client.Proxy = _dnsServer.Proxy;
|
||||
client.PreferIPv6 = _dnsServer.PreferIPv6;
|
||||
@@ -307,7 +318,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received RCODE=" + nsResponse.RCODE.ToString() + " for '" + (_name == "" ? "<root>" : _name) + "' stub zone refresh from: " + nsResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received RCODE=" + nsResponse.RCODE.ToString() + " for '" + (_name == "" ? "<root>" : _name) + "' stub zone refresh from: " + nsResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -316,7 +327,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server received an empty response for NS query for '" + (_name == "" ? "<root>" : _name) + "' stub zone from: " + nsResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server received an empty response for NS query for '" + (_name == "" ? "<root>" : _name) + "' stub zone from: " + nsResponse.Metadata.NameServer.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -342,7 +353,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server successfully refreshed '" + (_name == "" ? "<root>" : _name) + "' stub zone from: " + nsResponse.Metadata.NameServerAddress.ToString());
|
||||
log.Write("DNS Server successfully refreshed '" + (_name == "" ? "<root>" : _name) + "' stub zone from: " + nsResponse.Metadata.NameServer.ToString());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -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
|
||||
@@ -49,7 +49,7 @@ namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
foreach (DnsResourceRecord record in entry.Value)
|
||||
{
|
||||
if (!record.IsDisabled())
|
||||
if (!record.GetAuthRecordInfo().Disabled)
|
||||
{
|
||||
_disabled = false;
|
||||
return;
|
||||
|
||||
@@ -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
|
||||
@@ -50,6 +50,12 @@ namespace DnsServerCore.Dns.Zones
|
||||
_entries = new ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>>(1, capacity);
|
||||
}
|
||||
|
||||
protected Zone(string name, ConcurrentDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entries)
|
||||
{
|
||||
_name = name.ToLower();
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region static
|
||||
|
||||
@@ -12,9 +12,32 @@
|
||||
<RepositoryType></RepositoryType>
|
||||
<Description></Description>
|
||||
<PackageId>DnsServer</PackageId>
|
||||
<Version>10.0.1</Version>
|
||||
<Version>11.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.ByteTree">
|
||||
<HintPath>..\..\TechnitiumLibrary\bin\TechnitiumLibrary.ByteTree.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.IO">
|
||||
<HintPath>..\..\TechnitiumLibrary\bin\TechnitiumLibrary.IO.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="dohwww\css\bootstrap.min.css" />
|
||||
<None Remove="dohwww\css\bootstrap.min.css.map" />
|
||||
@@ -28,19 +51,11 @@
|
||||
<None Remove="dohwww\robots.txt" />
|
||||
<None Remove="named.root" />
|
||||
<None Remove="root-anchors.xml" />
|
||||
<None Remove="www\css\bootstrap-datetimepicker.min.css" />
|
||||
<None Remove="www\css\bootstrap-theme.min.css" />
|
||||
<None Remove="www\css\bootstrap-theme.min.css.map" />
|
||||
<None Remove="www\css\bootstrap.min.css" />
|
||||
<None Remove="www\css\bootstrap.min.css.map" />
|
||||
<None Remove="www\css\font-awesome.min.css" />
|
||||
<None Remove="www\css\images\ui-icons_444444_256x240.png" />
|
||||
<None Remove="www\css\images\ui-icons_555555_256x240.png" />
|
||||
<None Remove="www\css\images\ui-icons_777620_256x240.png" />
|
||||
<None Remove="www\css\images\ui-icons_777777_256x240.png" />
|
||||
<None Remove="www\css\images\ui-icons_cc0000_256x240.png" />
|
||||
<None Remove="www\css\images\ui-icons_ffffff_256x240.png" />
|
||||
<None Remove="www\css\jquery-ui.min.css" />
|
||||
<None Remove="www\css\main.css" />
|
||||
<None Remove="www\favicon.ico" />
|
||||
<None Remove="www\fonts\fontawesome-webfont.eot" />
|
||||
@@ -61,12 +76,10 @@
|
||||
<None Remove="www\index.html" />
|
||||
<None Remove="www\js\apps.js" />
|
||||
<None Remove="www\js\auth.js" />
|
||||
<None Remove="www\js\bootstrap-datetimepicker.min.js" />
|
||||
<None Remove="www\js\bootstrap.min.js" />
|
||||
<None Remove="www\js\Chart.min.js" />
|
||||
<None Remove="www\js\common.js" />
|
||||
<None Remove="www\js\dhcp.js" />
|
||||
<None Remove="www\js\jquery-ui.min.js" />
|
||||
<None Remove="www\js\jquery.min.js" />
|
||||
<None Remove="www\js\logs.js" />
|
||||
<None Remove="www\js\main.js" />
|
||||
@@ -113,30 +126,6 @@
|
||||
<Content Include="root-anchors.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\bootstrap-datetimepicker.min.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\images\ui-icons_444444_256x240.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\images\ui-icons_555555_256x240.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\images\ui-icons_777620_256x240.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\images\ui-icons_777777_256x240.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\images\ui-icons_cc0000_256x240.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\images\ui-icons_ffffff_256x240.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\jquery-ui.min.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\favicon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -206,9 +195,6 @@
|
||||
<Content Include="www\js\auth.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\js\bootstrap-datetimepicker.min.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\js\bootstrap.min.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -218,9 +204,6 @@
|
||||
<Content Include="www\js\common.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\js\jquery-ui.min.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\js\jquery.min.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -247,27 +230,4 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.ByteTree">
|
||||
<HintPath>..\..\TechnitiumLibrary\bin\TechnitiumLibrary.ByteTree.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.IO">
|
||||
<HintPath>..\..\TechnitiumLibrary\bin\TechnitiumLibrary.IO.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
236
DnsServerCore/Extensions.cs
Normal file
236
DnsServerCore/Extensions.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
using DnsServerCore.Auth;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using TechnitiumLibrary.Net;
|
||||
|
||||
namespace DnsServerCore
|
||||
{
|
||||
static class Extensions
|
||||
{
|
||||
readonly static string[] HTTP_METHODS = new string[] { "GET", "POST" };
|
||||
|
||||
public static IPEndPoint GetRemoteEndPoint(this HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPAddress remoteIP = context.Connection.RemoteIpAddress;
|
||||
if (remoteIP is null)
|
||||
return new IPEndPoint(IPAddress.Any, 0);
|
||||
|
||||
if (NetUtilities.IsPrivateIP(remoteIP))
|
||||
{
|
||||
string xRealIp = context.Request.Headers["X-Real-IP"];
|
||||
if (IPAddress.TryParse(xRealIp, out IPAddress address))
|
||||
{
|
||||
//get the real IP address of the requesting client from X-Real-IP header set in nginx proxy_pass block
|
||||
return new IPEndPoint(address, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return new IPEndPoint(remoteIP, context.Connection.RemotePort);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new IPEndPoint(IPAddress.Any, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static UserSession GetCurrentSession(this HttpContext context)
|
||||
{
|
||||
if (context.Items["session"] is UserSession userSession)
|
||||
return userSession;
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public static Utf8JsonWriter GetCurrentJsonWriter(this HttpContext context)
|
||||
{
|
||||
if (context.Items["jsonWriter"] is Utf8JsonWriter jsonWriter)
|
||||
return jsonWriter;
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public static IEndpointConventionBuilder MapGetAndPost(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate)
|
||||
{
|
||||
return endpoints.MapMethods(pattern, HTTP_METHODS, requestDelegate);
|
||||
}
|
||||
|
||||
public static IEndpointConventionBuilder MapGetAndPost(this IEndpointRouteBuilder endpoints, string pattern, Delegate handler)
|
||||
{
|
||||
return endpoints.MapMethods(pattern, HTTP_METHODS, handler);
|
||||
}
|
||||
|
||||
public static string QueryOrForm(this HttpRequest request, string parameter)
|
||||
{
|
||||
string value = request.Query[parameter];
|
||||
if ((value is null) && request.HasFormContentType)
|
||||
value = request.Form[parameter];
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static string GetQueryOrForm(this HttpRequest request, string parameter)
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new DnsWebServiceException("Parameter '" + parameter + "' missing.");
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static string GetQueryOrForm(this HttpRequest request, string parameter, string defaultValue)
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return defaultValue;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static T GetQueryOrForm<T>(this HttpRequest request, string parameter, Func<string, T> parse)
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new DnsWebServiceException("Parameter '" + parameter + "' missing.");
|
||||
|
||||
return parse(value);
|
||||
}
|
||||
|
||||
public static T GetQueryOrFormEnum<T>(this HttpRequest request, string parameter) where T : struct
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new DnsWebServiceException("Parameter '" + parameter + "' missing.");
|
||||
|
||||
return Enum.Parse<T>(value, true);
|
||||
}
|
||||
|
||||
public static T GetQueryOrForm<T>(this HttpRequest request, string parameter, Func<string, T> parse, T defaultValue)
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return defaultValue;
|
||||
|
||||
return parse(value);
|
||||
}
|
||||
|
||||
public static T GetQueryOrFormEnum<T>(this HttpRequest request, string parameter, T defaultValue) where T : struct
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return defaultValue;
|
||||
|
||||
return Enum.Parse<T>(value, true);
|
||||
}
|
||||
|
||||
public static bool TryGetQueryOrForm(this HttpRequest request, string parameter, out string value)
|
||||
{
|
||||
value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetQueryOrForm<T>(this HttpRequest request, string parameter, Func<string, T> parse, out T value)
|
||||
{
|
||||
string strValue = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(strValue))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = parse(strValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetQueryOrFormEnum<T>(this HttpRequest request, string parameter, out T value) where T : struct
|
||||
{
|
||||
string strValue = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(strValue))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return Enum.TryParse(strValue, true, out value);
|
||||
}
|
||||
|
||||
public static string GetQueryOrFormAlt(this HttpRequest request, string parameter, string alternateParameter)
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
value = request.QueryOrForm(alternateParameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new DnsWebServiceException("Parameter '" + parameter + "' missing.");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static string GetQueryOrFormAlt(this HttpRequest request, string parameter, string alternateParameter, string defaultValue)
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
value = request.QueryOrForm(alternateParameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static T GetQueryOrFormAlt<T>(this HttpRequest request, string parameter, string alternateParameter, Func<string, T> parse)
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
value = request.QueryOrForm(alternateParameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new DnsWebServiceException("Parameter '" + parameter + "' missing.");
|
||||
}
|
||||
|
||||
return parse(value);
|
||||
}
|
||||
|
||||
public static T GetQueryOrFormAlt<T>(this HttpRequest request, string parameter, string alternateParameter, Func<string, T> parse, T defaultValue)
|
||||
{
|
||||
string value = request.QueryOrForm(parameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
value = request.QueryOrForm(alternateParameter);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return parse(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -17,10 +17,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -434,29 +437,60 @@ namespace DnsServerCore
|
||||
return Directory.GetFiles(ConvertToAbsolutePath(_logFolder), "*.log", SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
public async Task DownloadLogAsync(HttpListenerRequest request, HttpListenerResponse response, string logName, long limit)
|
||||
public async Task DownloadLogAsync(HttpContext context, string logName, long limit)
|
||||
{
|
||||
string logFileName = logName + ".log";
|
||||
|
||||
using (FileStream fS = new FileStream(Path.Combine(ConvertToAbsolutePath(_logFolder), logFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 64 * 1024, true))
|
||||
{
|
||||
HttpResponse response = context.Response;
|
||||
|
||||
response.ContentType = "text/plain";
|
||||
response.AddHeader("Content-Disposition", "attachment;filename=" + logFileName);
|
||||
response.Headers.ContentDisposition = "attachment;filename=" + logFileName;
|
||||
|
||||
if ((limit > fS.Length) || (limit < 1))
|
||||
limit = fS.Length;
|
||||
|
||||
OffsetStream oFS = new OffsetStream(fS, 0, limit);
|
||||
HttpRequest request = context.Request;
|
||||
Stream s;
|
||||
|
||||
using (Stream s = DnsWebService.GetOutputStream(request, response))
|
||||
string acceptEncoding = request.Headers["Accept-Encoding"];
|
||||
if (string.IsNullOrEmpty(acceptEncoding))
|
||||
{
|
||||
s = response.Body;
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] acceptEncodingParts = acceptEncoding.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
if (acceptEncodingParts.Contains("br"))
|
||||
{
|
||||
response.Headers.ContentEncoding = "br";
|
||||
s = new BrotliStream(response.Body, CompressionMode.Compress);
|
||||
}
|
||||
else if (acceptEncodingParts.Contains("gzip"))
|
||||
{
|
||||
response.Headers.ContentEncoding = "gzip";
|
||||
s = new GZipStream(response.Body, CompressionMode.Compress);
|
||||
}
|
||||
else if (acceptEncodingParts.Contains("deflate"))
|
||||
{
|
||||
response.Headers.ContentEncoding = "deflate";
|
||||
s = new DeflateStream(response.Body, CompressionMode.Compress);
|
||||
}
|
||||
else
|
||||
{
|
||||
s = response.Body;
|
||||
}
|
||||
}
|
||||
|
||||
await using (s)
|
||||
{
|
||||
await oFS.CopyToAsync(s);
|
||||
|
||||
if (fS.Length > limit)
|
||||
{
|
||||
byte[] buffer = Encoding.UTF8.GetBytes("\r\n####___TRUNCATED___####");
|
||||
s.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
await s.WriteAsync(Encoding.UTF8.GetBytes("\r\n####___TRUNCATED___####"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,7 +626,7 @@ namespace DnsServerCore
|
||||
|
||||
EDnsClientSubnetOptionData responseECS = response.GetEDnsClientSubnetOption();
|
||||
if (responseECS is not null)
|
||||
answer += "; ECS: " + responseECS.Address.ToString() + "/" + responseECS.SourcePrefixLength;
|
||||
answer += "; ECS: " + responseECS.Address.ToString() + "/" + responseECS.ScopePrefixLength;
|
||||
|
||||
responseInfo = " RCODE: " + response.RCODE.ToString() + "; ANSWER: " + answer;
|
||||
}
|
||||
|
||||
384
DnsServerCore/WebServiceApi.cs
Normal file
384
DnsServerCore/WebServiceApi.cs
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
using DnsServerCore.Auth;
|
||||
using DnsServerCore.Dns;
|
||||
using DnsServerCore.Dns.ResourceRecords;
|
||||
using DnsServerCore.Dns.Zones;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using TechnitiumLibrary;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
using TechnitiumLibrary.Net.Proxy;
|
||||
|
||||
namespace DnsServerCore
|
||||
{
|
||||
class WebServiceApi
|
||||
{
|
||||
#region variables
|
||||
|
||||
readonly DnsWebService _dnsWebService;
|
||||
readonly Uri _updateCheckUri;
|
||||
|
||||
string _checkForUpdateJsonData;
|
||||
DateTime _checkForUpdateJsonDataUpdatedOn;
|
||||
const int CHECK_FOR_UPDATE_JSON_DATA_CACHE_TIME_SECONDS = 3600;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public WebServiceApi(DnsWebService dnsWebService, Uri updateCheckUri)
|
||||
{
|
||||
_dnsWebService = dnsWebService;
|
||||
_updateCheckUri = updateCheckUri;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
|
||||
private async Task<string> GetCheckForUpdateJsonData()
|
||||
{
|
||||
if ((_checkForUpdateJsonData is null) || (DateTime.UtcNow > _checkForUpdateJsonDataUpdatedOn.AddSeconds(CHECK_FOR_UPDATE_JSON_DATA_CACHE_TIME_SECONDS)))
|
||||
{
|
||||
SocketsHttpHandler handler = new SocketsHttpHandler();
|
||||
handler.Proxy = _dnsWebService.DnsServer.Proxy;
|
||||
handler.UseProxy = _dnsWebService.DnsServer.Proxy is not null;
|
||||
handler.AutomaticDecompression = DecompressionMethods.All;
|
||||
|
||||
using (HttpClient http = new HttpClient(handler))
|
||||
{
|
||||
_checkForUpdateJsonData = await http.GetStringAsync(_updateCheckUri);
|
||||
_checkForUpdateJsonDataUpdatedOn = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
return _checkForUpdateJsonData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public
|
||||
|
||||
public async Task CheckForUpdateAsync(HttpContext context)
|
||||
{
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
|
||||
if (_updateCheckUri is null)
|
||||
{
|
||||
jsonWriter.WriteBoolean("updateAvailable", false);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string jsonData = await GetCheckForUpdateJsonData();
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(jsonData);
|
||||
JsonElement jsonResponse = jsonDocument.RootElement;
|
||||
|
||||
string updateVersion = jsonResponse.GetProperty("updateVersion").GetString();
|
||||
string updateTitle = jsonResponse.GetPropertyValue("updateTitle", null);
|
||||
string updateMessage = jsonResponse.GetPropertyValue("updateMessage", null);
|
||||
string downloadLink = jsonResponse.GetPropertyValue("downloadLink", null);
|
||||
string instructionsLink = jsonResponse.GetPropertyValue("instructionsLink", null);
|
||||
string changeLogLink = jsonResponse.GetPropertyValue("changeLogLink", null);
|
||||
|
||||
bool updateAvailable = new Version(updateVersion) > _dnsWebService._currentVersion;
|
||||
|
||||
jsonWriter.WriteBoolean("updateAvailable", updateAvailable);
|
||||
jsonWriter.WriteString("updateVersion", updateVersion);
|
||||
jsonWriter.WriteString("currentVersion", _dnsWebService.GetServerVersion());
|
||||
|
||||
if (updateAvailable)
|
||||
{
|
||||
jsonWriter.WriteString("updateTitle", updateTitle);
|
||||
jsonWriter.WriteString("updateMessage", updateMessage);
|
||||
jsonWriter.WriteString("downloadLink", downloadLink);
|
||||
jsonWriter.WriteString("instructionsLink", instructionsLink);
|
||||
jsonWriter.WriteString("changeLogLink", changeLogLink);
|
||||
}
|
||||
|
||||
string strLog = "Check for update was done {updateAvailable: " + updateAvailable + "; updateVersion: " + updateVersion + ";";
|
||||
|
||||
if (!string.IsNullOrEmpty(updateTitle))
|
||||
strLog += " updateTitle: " + updateTitle + ";";
|
||||
|
||||
if (!string.IsNullOrEmpty(updateMessage))
|
||||
strLog += " updateMessage: " + updateMessage + ";";
|
||||
|
||||
if (!string.IsNullOrEmpty(downloadLink))
|
||||
strLog += " downloadLink: " + downloadLink + ";";
|
||||
|
||||
if (!string.IsNullOrEmpty(instructionsLink))
|
||||
strLog += " instructionsLink: " + instructionsLink + ";";
|
||||
|
||||
if (!string.IsNullOrEmpty(changeLogLink))
|
||||
strLog += " changeLogLink: " + changeLogLink + ";";
|
||||
|
||||
strLog += "}";
|
||||
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), strLog);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), "Check for update was done {updateAvailable: False;}\r\n" + ex.ToString());
|
||||
|
||||
jsonWriter.WriteBoolean("updateAvailable", false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ResolveQueryAsync(HttpContext context)
|
||||
{
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.DnsClient, session.User, PermissionFlag.View))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
string server = request.GetQueryOrForm("server");
|
||||
string domain = request.GetQueryOrForm("domain").Trim(new char[] { '\t', ' ', '.' });
|
||||
DnsResourceRecordType type = request.GetQueryOrFormEnum<DnsResourceRecordType>("type");
|
||||
DnsTransportProtocol protocol = request.GetQueryOrFormEnum("protocol", DnsTransportProtocol.Udp);
|
||||
bool dnssecValidation = request.GetQueryOrForm("dnssec", bool.Parse, false);
|
||||
bool importResponse = request.GetQueryOrForm("import", bool.Parse, false);
|
||||
NetProxy proxy = _dnsWebService.DnsServer.Proxy;
|
||||
bool preferIPv6 = _dnsWebService.DnsServer.PreferIPv6;
|
||||
ushort udpPayloadSize = _dnsWebService.DnsServer.UdpPayloadSize;
|
||||
bool randomizeName = false;
|
||||
bool qnameMinimization = _dnsWebService.DnsServer.QnameMinimization;
|
||||
const int RETRIES = 1;
|
||||
const int TIMEOUT = 10000;
|
||||
|
||||
DnsDatagram dnsResponse;
|
||||
string dnssecErrorMessage = null;
|
||||
|
||||
if (server.Equals("recursive-resolver", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (type == DnsResourceRecordType.AXFR)
|
||||
throw new DnsServerException("Cannot do zone transfer (AXFR) for 'recursive-resolver'.");
|
||||
|
||||
DnsQuestionRecord question;
|
||||
|
||||
if ((type == DnsResourceRecordType.PTR) && IPAddress.TryParse(domain, out IPAddress address))
|
||||
question = new DnsQuestionRecord(address, DnsClass.IN);
|
||||
else
|
||||
question = new DnsQuestionRecord(domain, type, DnsClass.IN);
|
||||
|
||||
DnsCache dnsCache = new DnsCache();
|
||||
dnsCache.MinimumRecordTtl = 0;
|
||||
dnsCache.MaximumRecordTtl = 7 * 24 * 60 * 60;
|
||||
|
||||
try
|
||||
{
|
||||
dnsResponse = await DnsClient.RecursiveResolveAsync(question, dnsCache, proxy, preferIPv6, udpPayloadSize, randomizeName, qnameMinimization, false, dnssecValidation, null, RETRIES, TIMEOUT);
|
||||
}
|
||||
catch (DnsClientResponseDnssecValidationException ex)
|
||||
{
|
||||
dnsResponse = ex.Response;
|
||||
dnssecErrorMessage = ex.Message;
|
||||
importResponse = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((type == DnsResourceRecordType.AXFR) && (protocol == DnsTransportProtocol.Udp))
|
||||
protocol = DnsTransportProtocol.Tcp;
|
||||
|
||||
NameServerAddress nameServer;
|
||||
|
||||
if (server.Equals("this-server", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
switch (protocol)
|
||||
{
|
||||
case DnsTransportProtocol.Udp:
|
||||
nameServer = _dnsWebService.DnsServer.ThisServer;
|
||||
break;
|
||||
|
||||
case DnsTransportProtocol.Tcp:
|
||||
nameServer = _dnsWebService.DnsServer.ThisServer.ChangeProtocol(DnsTransportProtocol.Tcp);
|
||||
break;
|
||||
|
||||
case DnsTransportProtocol.Tls:
|
||||
throw new DnsServerException("Cannot use DNS-over-TLS protocol for 'this-server'. Please use the TLS certificate domain name as the server.");
|
||||
|
||||
case DnsTransportProtocol.Https:
|
||||
throw new DnsServerException("Cannot use DNS-over-HTTPS protocol for 'this-server'. Please use the TLS certificate domain name with a url as the server.");
|
||||
|
||||
case DnsTransportProtocol.Quic:
|
||||
throw new DnsServerException("Cannot use DNS-over-QUIC protocol for 'this-server'. Please use the TLS certificate domain name as the server.");
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("DNS transport protocol is not supported: " + protocol.ToString());
|
||||
}
|
||||
|
||||
proxy = null; //no proxy required for this server
|
||||
}
|
||||
else
|
||||
{
|
||||
nameServer = NameServerAddress.Parse(server);
|
||||
|
||||
if (nameServer.Protocol != protocol)
|
||||
nameServer = nameServer.ChangeProtocol(protocol);
|
||||
|
||||
if (nameServer.IsIPEndPointStale)
|
||||
{
|
||||
if (proxy is null)
|
||||
await nameServer.ResolveIPAddressAsync(_dnsWebService.DnsServer, _dnsWebService.DnsServer.PreferIPv6);
|
||||
}
|
||||
else if ((nameServer.DomainEndPoint is null) && ((protocol == DnsTransportProtocol.Udp) || (protocol == DnsTransportProtocol.Tcp)))
|
||||
{
|
||||
try
|
||||
{
|
||||
await nameServer.ResolveDomainNameAsync(_dnsWebService.DnsServer);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
DnsClient dnsClient = new DnsClient(nameServer);
|
||||
|
||||
dnsClient.Proxy = proxy;
|
||||
dnsClient.PreferIPv6 = preferIPv6;
|
||||
dnsClient.RandomizeName = randomizeName;
|
||||
dnsClient.Retries = RETRIES;
|
||||
dnsClient.Timeout = TIMEOUT;
|
||||
dnsClient.UdpPayloadSize = udpPayloadSize;
|
||||
dnsClient.DnssecValidation = dnssecValidation;
|
||||
|
||||
if (dnssecValidation)
|
||||
{
|
||||
//load trust anchors into dns client if domain is locally hosted
|
||||
_dnsWebService.DnsServer.AuthZoneManager.LoadTrustAnchorsTo(dnsClient, domain, type);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
dnsResponse = await dnsClient.ResolveAsync(domain, type);
|
||||
}
|
||||
catch (DnsClientResponseDnssecValidationException ex)
|
||||
{
|
||||
dnsResponse = ex.Response;
|
||||
dnssecErrorMessage = ex.Message;
|
||||
importResponse = false;
|
||||
}
|
||||
|
||||
if (type == DnsResourceRecordType.AXFR)
|
||||
dnsResponse = dnsResponse.Join();
|
||||
}
|
||||
|
||||
if (importResponse)
|
||||
{
|
||||
AuthZoneInfo zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.FindAuthZoneInfo(domain);
|
||||
if ((zoneInfo is null) || ((zoneInfo.Type == AuthZoneType.Secondary) && !zoneInfo.Name.Equals(domain, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.CreatePrimaryZone(domain, _dnsWebService.DnsServer.ServerDomain, false);
|
||||
if (zoneInfo is null)
|
||||
throw new DnsServerException("Cannot import records: failed to create primary zone.");
|
||||
|
||||
//set permissions
|
||||
_dnsWebService._authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete);
|
||||
_dnsWebService._authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService._authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
|
||||
_dnsWebService._authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService._authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
|
||||
_dnsWebService._authManager.SaveConfigFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Modify))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
break;
|
||||
|
||||
case AuthZoneType.Forwarder:
|
||||
if (type == DnsResourceRecordType.AXFR)
|
||||
throw new DnsServerException("Cannot import records via zone transfer: import zone must be of primary type.");
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new DnsServerException("Cannot import records: import zone must be of primary or forwarder type.");
|
||||
}
|
||||
}
|
||||
|
||||
if (type == DnsResourceRecordType.AXFR)
|
||||
{
|
||||
_dnsWebService.DnsServer.AuthZoneManager.SyncZoneTransferRecords(zoneInfo.Name, dnsResponse.Answer);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<DnsResourceRecord> importRecords = new List<DnsResourceRecord>(dnsResponse.Answer.Count + dnsResponse.Authority.Count);
|
||||
|
||||
foreach (DnsResourceRecord record in dnsResponse.Answer)
|
||||
{
|
||||
if (record.Name.Equals(zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || (zoneInfo.Name.Length == 0))
|
||||
{
|
||||
record.RemoveExpiry();
|
||||
importRecords.Add(record);
|
||||
|
||||
if (record.Type == DnsResourceRecordType.NS)
|
||||
record.SyncGlueRecords(dnsResponse.Additional);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (DnsResourceRecord record in dnsResponse.Authority)
|
||||
{
|
||||
if (record.Name.Equals(zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || (zoneInfo.Name.Length == 0))
|
||||
{
|
||||
record.RemoveExpiry();
|
||||
importRecords.Add(record);
|
||||
|
||||
if (record.Type == DnsResourceRecordType.NS)
|
||||
record.SyncGlueRecords(dnsResponse.Additional);
|
||||
}
|
||||
}
|
||||
|
||||
_dnsWebService.DnsServer.AuthZoneManager.ImportRecords(zoneInfo.Name, importRecords);
|
||||
}
|
||||
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), "[" + session.User.Username + "] DNS Client imported record(s) for authoritative zone {server: " + server + "; zone: " + zoneInfo.Name + "; type: " + type + ";}");
|
||||
|
||||
_dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
|
||||
}
|
||||
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
|
||||
if (dnssecErrorMessage is not null)
|
||||
jsonWriter.WriteString("warningMessage", dnssecErrorMessage);
|
||||
|
||||
jsonWriter.WritePropertyName("result");
|
||||
dnsResponse.SerializeTo(jsonWriter);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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,17 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using DnsServerCore.ApplicationCommon;
|
||||
using DnsServerCore.Auth;
|
||||
using DnsServerCore.Dns.Applications;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TechnitiumLibrary;
|
||||
using TechnitiumLibrary.IO;
|
||||
|
||||
namespace DnsServerCore
|
||||
{
|
||||
@@ -41,7 +42,7 @@ namespace DnsServerCore
|
||||
|
||||
string _storeAppsJsonData;
|
||||
DateTime _storeAppsJsonDataUpdatedOn;
|
||||
const int STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS = 300;
|
||||
const int STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS = 900;
|
||||
|
||||
Timer _appUpdateTimer;
|
||||
const int APP_UPDATE_TIMER_INITIAL_INTERVAL = 10000;
|
||||
@@ -89,35 +90,36 @@ namespace DnsServerCore
|
||||
if (_dnsWebService.DnsServer.DnsApplicationManager.Applications.Count < 1)
|
||||
return;
|
||||
|
||||
_dnsWebService.Log.Write("DNS Server has started automatic update check for DNS Apps.");
|
||||
_dnsWebService._log.Write("DNS Server has started automatic update check for DNS Apps.");
|
||||
|
||||
string storeAppsJsonData = await GetStoreAppsJsonData().WithTimeout(5000);
|
||||
dynamic jsonStoreAppsArray = JsonConvert.DeserializeObject(storeAppsJsonData);
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(storeAppsJsonData);
|
||||
JsonElement jsonStoreAppsArray = jsonDocument.RootElement;
|
||||
|
||||
foreach (DnsApplication application in _dnsWebService.DnsServer.DnsApplicationManager.Applications.Values)
|
||||
{
|
||||
foreach (dynamic jsonStoreApp in jsonStoreAppsArray)
|
||||
foreach (JsonElement jsonStoreApp in jsonStoreAppsArray.EnumerateArray())
|
||||
{
|
||||
string name = jsonStoreApp.name.Value;
|
||||
string name = jsonStoreApp.GetProperty("name").GetString();
|
||||
if (name.Equals(application.Name))
|
||||
{
|
||||
string url = null;
|
||||
Version storeAppVersion = null;
|
||||
Version lastServerVersion = null;
|
||||
|
||||
foreach (dynamic jsonVersion in jsonStoreApp.versions)
|
||||
foreach (JsonElement jsonVersion in jsonStoreApp.GetProperty("versions").EnumerateArray())
|
||||
{
|
||||
string strServerVersion = jsonVersion.serverVersion.Value;
|
||||
string strServerVersion = jsonVersion.GetProperty("serverVersion").GetString();
|
||||
Version requiredServerVersion = new Version(strServerVersion);
|
||||
|
||||
if (_dnsWebService.ServerVersion < requiredServerVersion)
|
||||
if (_dnsWebService._currentVersion < requiredServerVersion)
|
||||
continue;
|
||||
|
||||
if ((lastServerVersion is not null) && (lastServerVersion > requiredServerVersion))
|
||||
continue;
|
||||
|
||||
string version = jsonVersion.version.Value;
|
||||
url = jsonVersion.url.Value;
|
||||
string version = jsonVersion.GetProperty("version").GetString();
|
||||
url = jsonVersion.GetProperty("url").GetString();
|
||||
|
||||
storeAppVersion = new Version(version);
|
||||
lastServerVersion = requiredServerVersion;
|
||||
@@ -129,11 +131,11 @@ namespace DnsServerCore
|
||||
{
|
||||
await DownloadAndUpdateAppAsync(application.Name, url);
|
||||
|
||||
_dnsWebService.Log.Write("DNS application '" + application.Name + "' was automatically updated successfully from: " + url);
|
||||
_dnsWebService._log.Write("DNS application '" + application.Name + "' was automatically updated successfully from: " + url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dnsWebService.Log.Write("Failed to automatically download and update DNS application '" + application.Name + "': " + ex.ToString());
|
||||
_dnsWebService._log.Write("Failed to automatically download and update DNS application '" + application.Name + "': " + ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +146,7 @@ namespace DnsServerCore
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dnsWebService.Log.Write(ex);
|
||||
_dnsWebService._log.Write(ex);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -163,12 +165,12 @@ namespace DnsServerCore
|
||||
|
||||
private async Task<string> GetStoreAppsJsonData()
|
||||
{
|
||||
if ((_storeAppsJsonData == null) || (DateTime.UtcNow > _storeAppsJsonDataUpdatedOn.AddSeconds(STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS)))
|
||||
if ((_storeAppsJsonData is null) || (DateTime.UtcNow > _storeAppsJsonDataUpdatedOn.AddSeconds(STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS)))
|
||||
{
|
||||
SocketsHttpHandler handler = new SocketsHttpHandler();
|
||||
handler.Proxy = _dnsWebService.DnsServer.Proxy;
|
||||
handler.UseProxy = _dnsWebService.DnsServer.Proxy is not null;
|
||||
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
handler.AutomaticDecompression = DecompressionMethods.All;
|
||||
|
||||
using (HttpClient http = new HttpClient(handler))
|
||||
{
|
||||
@@ -191,7 +193,7 @@ namespace DnsServerCore
|
||||
SocketsHttpHandler handler = new SocketsHttpHandler();
|
||||
handler.Proxy = _dnsWebService.DnsServer.Proxy;
|
||||
handler.UseProxy = _dnsWebService.DnsServer.Proxy is not null;
|
||||
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
handler.AutomaticDecompression = DecompressionMethods.All;
|
||||
|
||||
using (HttpClient http = new HttpClient(handler))
|
||||
{
|
||||
@@ -214,29 +216,24 @@ namespace DnsServerCore
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dnsWebService.Log.Write(ex);
|
||||
_dnsWebService._log.Write(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteAppAsJson(JsonTextWriter jsonWriter, DnsApplication application, dynamic jsonStoreAppsArray)
|
||||
private void WriteAppAsJson(Utf8JsonWriter jsonWriter, DnsApplication application, JsonElement jsonStoreAppsArray = default)
|
||||
{
|
||||
jsonWriter.WriteStartObject();
|
||||
|
||||
jsonWriter.WritePropertyName("name");
|
||||
jsonWriter.WriteValue(application.Name);
|
||||
|
||||
jsonWriter.WritePropertyName("description");
|
||||
jsonWriter.WriteValue(application.Description);
|
||||
jsonWriter.WriteString("name", application.Name);
|
||||
jsonWriter.WriteString("description", application.Description);
|
||||
jsonWriter.WriteString("version", DnsWebService.GetCleanVersion(application.Version));
|
||||
|
||||
jsonWriter.WritePropertyName("version");
|
||||
jsonWriter.WriteValue(DnsWebService.GetCleanVersion(application.Version));
|
||||
|
||||
if (jsonStoreAppsArray != null)
|
||||
if (jsonStoreAppsArray.ValueKind != JsonValueKind.Undefined)
|
||||
{
|
||||
foreach (dynamic jsonStoreApp in jsonStoreAppsArray)
|
||||
foreach (JsonElement jsonStoreApp in jsonStoreAppsArray.EnumerateArray())
|
||||
{
|
||||
string name = jsonStoreApp.name.Value;
|
||||
string name = jsonStoreApp.GetProperty("name").GetString();
|
||||
if (name.Equals(application.Name))
|
||||
{
|
||||
string version = null;
|
||||
@@ -244,19 +241,19 @@ namespace DnsServerCore
|
||||
Version storeAppVersion = null;
|
||||
Version lastServerVersion = null;
|
||||
|
||||
foreach (dynamic jsonVersion in jsonStoreApp.versions)
|
||||
foreach (JsonElement jsonVersion in jsonStoreApp.GetProperty("versions").EnumerateArray())
|
||||
{
|
||||
string strServerVersion = jsonVersion.serverVersion.Value;
|
||||
string strServerVersion = jsonVersion.GetProperty("serverVersion").GetString();
|
||||
Version requiredServerVersion = new Version(strServerVersion);
|
||||
|
||||
if (_dnsWebService.ServerVersion < requiredServerVersion)
|
||||
if (_dnsWebService._currentVersion < requiredServerVersion)
|
||||
continue;
|
||||
|
||||
if ((lastServerVersion is not null) && (lastServerVersion > requiredServerVersion))
|
||||
continue;
|
||||
|
||||
version = jsonVersion.version.Value;
|
||||
url = jsonVersion.url.Value;
|
||||
version = jsonVersion.GetProperty("version").GetString();
|
||||
url = jsonVersion.GetProperty("url").GetString();
|
||||
|
||||
storeAppVersion = new Version(version);
|
||||
lastServerVersion = requiredServerVersion;
|
||||
@@ -265,14 +262,9 @@ namespace DnsServerCore
|
||||
if (storeAppVersion is null)
|
||||
break; //no compatible update available
|
||||
|
||||
jsonWriter.WritePropertyName("updateVersion");
|
||||
jsonWriter.WriteValue(version);
|
||||
|
||||
jsonWriter.WritePropertyName("updateUrl");
|
||||
jsonWriter.WriteValue(url);
|
||||
|
||||
jsonWriter.WritePropertyName("updateAvailable");
|
||||
jsonWriter.WriteValue(storeAppVersion > application.Version);
|
||||
jsonWriter.WriteString("updateVersion", version);
|
||||
jsonWriter.WriteString("updateUrl", url);
|
||||
jsonWriter.WriteBoolean("updateAvailable", storeAppVersion > application.Version);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -286,37 +278,23 @@ namespace DnsServerCore
|
||||
{
|
||||
jsonWriter.WriteStartObject();
|
||||
|
||||
jsonWriter.WritePropertyName("classPath");
|
||||
jsonWriter.WriteValue(dnsApp.Key);
|
||||
|
||||
jsonWriter.WritePropertyName("description");
|
||||
jsonWriter.WriteValue(dnsApp.Value.Description);
|
||||
jsonWriter.WriteString("classPath", dnsApp.Key);
|
||||
jsonWriter.WriteString("description", dnsApp.Value.Description);
|
||||
|
||||
if (dnsApp.Value is IDnsAppRecordRequestHandler appRecordHandler)
|
||||
{
|
||||
jsonWriter.WritePropertyName("isAppRecordRequestHandler");
|
||||
jsonWriter.WriteValue(true);
|
||||
|
||||
jsonWriter.WritePropertyName("recordDataTemplate");
|
||||
jsonWriter.WriteValue(appRecordHandler.ApplicationRecordDataTemplate);
|
||||
jsonWriter.WriteBoolean("isAppRecordRequestHandler", true);
|
||||
jsonWriter.WriteString("recordDataTemplate", appRecordHandler.ApplicationRecordDataTemplate);
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonWriter.WritePropertyName("isAppRecordRequestHandler");
|
||||
jsonWriter.WriteValue(false);
|
||||
jsonWriter.WriteBoolean("isAppRecordRequestHandler", false);
|
||||
}
|
||||
|
||||
jsonWriter.WritePropertyName("isRequestController");
|
||||
jsonWriter.WriteValue(dnsApp.Value is IDnsRequestController);
|
||||
|
||||
jsonWriter.WritePropertyName("isAuthoritativeRequestHandler");
|
||||
jsonWriter.WriteValue(dnsApp.Value is IDnsAuthoritativeRequestHandler);
|
||||
|
||||
jsonWriter.WritePropertyName("isQueryLogger");
|
||||
jsonWriter.WriteValue(dnsApp.Value is IDnsQueryLogger);
|
||||
|
||||
jsonWriter.WritePropertyName("isPostProcessor");
|
||||
jsonWriter.WriteValue(dnsApp.Value is IDnsPostProcessor);
|
||||
jsonWriter.WriteBoolean("isRequestController", dnsApp.Value is IDnsRequestController);
|
||||
jsonWriter.WriteBoolean("isAuthoritativeRequestHandler", dnsApp.Value is IDnsAuthoritativeRequestHandler);
|
||||
jsonWriter.WriteBoolean("isQueryLogger", dnsApp.Value is IDnsQueryLogger);
|
||||
jsonWriter.WriteBoolean("isPostProcessor", dnsApp.Value is IDnsPostProcessor);
|
||||
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
@@ -331,68 +309,99 @@ namespace DnsServerCore
|
||||
|
||||
#region public
|
||||
|
||||
public async Task ListInstalledAppsAsync(JsonTextWriter jsonWriter)
|
||||
public async Task ListInstalledAppsAsync(HttpContext context)
|
||||
{
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
if (
|
||||
!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.View) &&
|
||||
!_dnsWebService._authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.View) &&
|
||||
!_dnsWebService._authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)
|
||||
)
|
||||
{
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
}
|
||||
|
||||
List<string> apps = new List<string>(_dnsWebService.DnsServer.DnsApplicationManager.Applications.Keys);
|
||||
apps.Sort();
|
||||
|
||||
dynamic jsonStoreAppsArray = null;
|
||||
|
||||
if (apps.Count > 0)
|
||||
JsonDocument jsonDocument = null;
|
||||
try
|
||||
{
|
||||
try
|
||||
JsonElement jsonStoreAppsArray = default;
|
||||
|
||||
if (apps.Count > 0)
|
||||
{
|
||||
string storeAppsJsonData = await GetStoreAppsJsonData().WithTimeout(5000);
|
||||
jsonStoreAppsArray = JsonConvert.DeserializeObject(storeAppsJsonData);
|
||||
try
|
||||
{
|
||||
string storeAppsJsonData = await GetStoreAppsJsonData().WithTimeout(5000);
|
||||
jsonDocument = JsonDocument.Parse(storeAppsJsonData);
|
||||
jsonStoreAppsArray = jsonDocument.RootElement;
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
|
||||
jsonWriter.WritePropertyName("apps");
|
||||
jsonWriter.WriteStartArray();
|
||||
|
||||
foreach (string app in apps)
|
||||
{
|
||||
if (_dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(app, out DnsApplication application))
|
||||
WriteAppAsJson(jsonWriter, application, jsonStoreAppsArray);
|
||||
}
|
||||
|
||||
jsonWriter.WriteEndArray();
|
||||
}
|
||||
|
||||
jsonWriter.WritePropertyName("apps");
|
||||
jsonWriter.WriteStartArray();
|
||||
|
||||
foreach (string app in apps)
|
||||
finally
|
||||
{
|
||||
if (_dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(app, out DnsApplication application))
|
||||
WriteAppAsJson(jsonWriter, application, jsonStoreAppsArray);
|
||||
if (jsonDocument is not null)
|
||||
jsonDocument.Dispose();
|
||||
}
|
||||
|
||||
jsonWriter.WriteEndArray();
|
||||
}
|
||||
|
||||
public async Task ListStoreApps(JsonTextWriter jsonWriter)
|
||||
public async Task ListStoreApps(HttpContext context)
|
||||
{
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.View))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
string storeAppsJsonData = await GetStoreAppsJsonData();
|
||||
dynamic jsonStoreAppsArray = JsonConvert.DeserializeObject(storeAppsJsonData);
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(storeAppsJsonData);
|
||||
JsonElement jsonStoreAppsArray = jsonDocument.RootElement;
|
||||
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
|
||||
jsonWriter.WritePropertyName("storeApps");
|
||||
jsonWriter.WriteStartArray();
|
||||
|
||||
foreach (dynamic jsonStoreApp in jsonStoreAppsArray)
|
||||
foreach (JsonElement jsonStoreApp in jsonStoreAppsArray.EnumerateArray())
|
||||
{
|
||||
string name = jsonStoreApp.name.Value;
|
||||
string description = jsonStoreApp.description.Value;
|
||||
string name = jsonStoreApp.GetProperty("name").GetString();
|
||||
string description = jsonStoreApp.GetProperty("description").GetString();
|
||||
string version = null;
|
||||
string url = null;
|
||||
string size = null;
|
||||
Version storeAppVersion = null;
|
||||
Version lastServerVersion = null;
|
||||
|
||||
foreach (dynamic jsonVersion in jsonStoreApp.versions)
|
||||
foreach (JsonElement jsonVersion in jsonStoreApp.GetProperty("versions").EnumerateArray())
|
||||
{
|
||||
string strServerVersion = jsonVersion.serverVersion.Value;
|
||||
string strServerVersion = jsonVersion.GetProperty("serverVersion").GetString();
|
||||
Version requiredServerVersion = new Version(strServerVersion);
|
||||
|
||||
if (_dnsWebService.ServerVersion < requiredServerVersion)
|
||||
if (_dnsWebService._currentVersion < requiredServerVersion)
|
||||
continue;
|
||||
|
||||
if ((lastServerVersion is not null) && (lastServerVersion > requiredServerVersion))
|
||||
continue;
|
||||
|
||||
version = jsonVersion.version.Value;
|
||||
url = jsonVersion.url.Value;
|
||||
size = jsonVersion.size.Value;
|
||||
version = jsonVersion.GetProperty("version").GetString();
|
||||
url = jsonVersion.GetProperty("url").GetString();
|
||||
size = jsonVersion.GetProperty("size").GetString();
|
||||
|
||||
storeAppVersion = new Version(version);
|
||||
lastServerVersion = requiredServerVersion;
|
||||
@@ -403,33 +412,20 @@ namespace DnsServerCore
|
||||
|
||||
jsonWriter.WriteStartObject();
|
||||
|
||||
jsonWriter.WritePropertyName("name");
|
||||
jsonWriter.WriteValue(name);
|
||||
|
||||
jsonWriter.WritePropertyName("description");
|
||||
jsonWriter.WriteValue(description);
|
||||
|
||||
jsonWriter.WritePropertyName("version");
|
||||
jsonWriter.WriteValue(version);
|
||||
|
||||
jsonWriter.WritePropertyName("url");
|
||||
jsonWriter.WriteValue(url);
|
||||
|
||||
jsonWriter.WritePropertyName("size");
|
||||
jsonWriter.WriteValue(size);
|
||||
jsonWriter.WriteString("name", name);
|
||||
jsonWriter.WriteString("description", description);
|
||||
jsonWriter.WriteString("version", version);
|
||||
jsonWriter.WriteString("url", url);
|
||||
jsonWriter.WriteString("size", size);
|
||||
|
||||
bool installed = _dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication installedApp);
|
||||
|
||||
jsonWriter.WritePropertyName("installed");
|
||||
jsonWriter.WriteValue(installed);
|
||||
jsonWriter.WriteBoolean("installed", installed);
|
||||
|
||||
if (installed)
|
||||
{
|
||||
jsonWriter.WritePropertyName("installedVersion");
|
||||
jsonWriter.WriteValue(DnsWebService.GetCleanVersion(installedApp.Version));
|
||||
|
||||
jsonWriter.WritePropertyName("updateAvailable");
|
||||
jsonWriter.WriteValue(storeAppVersion > installedApp.Version);
|
||||
jsonWriter.WriteString("installedVersion", DnsWebService.GetCleanVersion(installedApp.Version));
|
||||
jsonWriter.WriteBoolean("updateAvailable", storeAppVersion > installedApp.Version);
|
||||
}
|
||||
|
||||
jsonWriter.WriteEndObject();
|
||||
@@ -438,17 +434,17 @@ namespace DnsServerCore
|
||||
jsonWriter.WriteEndArray();
|
||||
}
|
||||
|
||||
public async Task DownloadAndInstallAppAsync(HttpListenerRequest request, JsonTextWriter jsonWriter)
|
||||
public async Task DownloadAndInstallAppAsync(HttpContext context)
|
||||
{
|
||||
string name = request.QueryString["name"];
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new DnsWebServiceException("Parameter 'name' missing.");
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
name = name.Trim();
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
string url = request.QueryString["url"];
|
||||
if (string.IsNullOrEmpty(url))
|
||||
throw new DnsWebServiceException("Parameter 'url' missing.");
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
string name = request.GetQueryOrForm("name").Trim();
|
||||
string url = request.GetQueryOrForm("url");
|
||||
|
||||
if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
throw new DnsWebServiceException("Parameter 'url' value must start with 'https://'.");
|
||||
@@ -462,7 +458,7 @@ namespace DnsServerCore
|
||||
SocketsHttpHandler handler = new SocketsHttpHandler();
|
||||
handler.Proxy = _dnsWebService.DnsServer.Proxy;
|
||||
handler.UseProxy = _dnsWebService.DnsServer.Proxy is not null;
|
||||
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
handler.AutomaticDecompression = DecompressionMethods.All;
|
||||
|
||||
using (HttpClient http = new HttpClient(handler))
|
||||
{
|
||||
@@ -476,10 +472,12 @@ namespace DnsServerCore
|
||||
fS.Position = 0;
|
||||
DnsApplication application = await _dnsWebService.DnsServer.DnsApplicationManager.InstallApplicationAsync(name, fS);
|
||||
|
||||
_dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was installed successfully from: " + url);
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), "[" + session.User.Username + "] DNS application '" + name + "' was installed successfully from: " + url);
|
||||
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
|
||||
jsonWriter.WritePropertyName("installedApp");
|
||||
WriteAppAsJson(jsonWriter, application, null);
|
||||
WriteAppAsJson(jsonWriter, application);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -490,64 +488,49 @@ namespace DnsServerCore
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dnsWebService.Log.Write(ex);
|
||||
_dnsWebService._log.Write(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DownloadAndUpdateAppAsync(HttpListenerRequest request, JsonTextWriter jsonWriter)
|
||||
public async Task DownloadAndUpdateAppAsync(HttpContext context)
|
||||
{
|
||||
string name = request.QueryString["name"];
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new DnsWebServiceException("Parameter 'name' missing.");
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
name = name.Trim();
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
string url = request.QueryString["url"];
|
||||
if (string.IsNullOrEmpty(url))
|
||||
throw new DnsWebServiceException("Parameter 'url' missing.");
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
string name = request.GetQueryOrForm("name").Trim();
|
||||
string url = request.GetQueryOrForm("url");
|
||||
|
||||
if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
throw new DnsWebServiceException("Parameter 'url' value must start with 'https://'.");
|
||||
|
||||
DnsApplication application = await DownloadAndUpdateAppAsync(name, url);
|
||||
|
||||
_dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was updated successfully from: " + url);
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), "[" + session.User.Username + "] DNS application '" + name + "' was updated successfully from: " + url);
|
||||
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
|
||||
jsonWriter.WritePropertyName("updatedApp");
|
||||
WriteAppAsJson(jsonWriter, application, null);
|
||||
WriteAppAsJson(jsonWriter, application);
|
||||
}
|
||||
|
||||
public async Task InstallAppAsync(HttpListenerRequest request, JsonTextWriter jsonWriter)
|
||||
public async Task InstallAppAsync(HttpContext context)
|
||||
{
|
||||
string name = request.QueryString["name"];
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new DnsWebServiceException("Parameter 'name' missing.");
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
name = name.Trim();
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
#region skip to content
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
int crlfCount = 0;
|
||||
int byteRead;
|
||||
string name = request.GetQueryOrForm("name").Trim();
|
||||
|
||||
while (crlfCount != 4)
|
||||
{
|
||||
byteRead = await request.InputStream.ReadByteValueAsync();
|
||||
switch (byteRead)
|
||||
{
|
||||
case 13: //CR
|
||||
case 10: //LF
|
||||
crlfCount++;
|
||||
break;
|
||||
|
||||
default:
|
||||
crlfCount = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
if (!request.HasFormContentType || (request.Form.Files.Count == 0))
|
||||
throw new DnsWebServiceException("DNS application zip file is missing.");
|
||||
|
||||
string tmpFile = Path.GetTempFileName();
|
||||
try
|
||||
@@ -555,16 +538,18 @@ namespace DnsServerCore
|
||||
using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
//write to temp file
|
||||
await request.InputStream.CopyToAsync(fS);
|
||||
await request.Form.Files[0].CopyToAsync(fS);
|
||||
|
||||
//install app
|
||||
fS.Position = 0;
|
||||
DnsApplication application = await _dnsWebService.DnsServer.DnsApplicationManager.InstallApplicationAsync(name, fS);
|
||||
|
||||
_dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was installed successfully.");
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), "[" + session.User.Username + "] DNS application '" + name + "' was installed successfully.");
|
||||
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
|
||||
jsonWriter.WritePropertyName("installedApp");
|
||||
WriteAppAsJson(jsonWriter, application, null);
|
||||
WriteAppAsJson(jsonWriter, application);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -575,41 +560,24 @@ namespace DnsServerCore
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dnsWebService.Log.Write(ex);
|
||||
_dnsWebService._log.Write(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateAppAsync(HttpListenerRequest request, JsonTextWriter jsonWriter)
|
||||
public async Task UpdateAppAsync(HttpContext context)
|
||||
{
|
||||
string name = request.QueryString["name"];
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new DnsWebServiceException("Parameter 'name' missing.");
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
name = name.Trim();
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
#region skip to content
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
int crlfCount = 0;
|
||||
int byteRead;
|
||||
string name = request.GetQueryOrForm("name").Trim();
|
||||
|
||||
while (crlfCount != 4)
|
||||
{
|
||||
byteRead = await request.InputStream.ReadByteValueAsync();
|
||||
switch (byteRead)
|
||||
{
|
||||
case 13: //CR
|
||||
case 10: //LF
|
||||
crlfCount++;
|
||||
break;
|
||||
|
||||
default:
|
||||
crlfCount = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
if (!request.HasFormContentType || (request.Form.Files.Count == 0))
|
||||
throw new DnsWebServiceException("DNS application zip file is missing.");
|
||||
|
||||
string tmpFile = Path.GetTempFileName();
|
||||
try
|
||||
@@ -617,16 +585,18 @@ namespace DnsServerCore
|
||||
using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
//write to temp file
|
||||
await request.InputStream.CopyToAsync(fS);
|
||||
await request.Form.Files[0].CopyToAsync(fS);
|
||||
|
||||
//update app
|
||||
fS.Position = 0;
|
||||
DnsApplication application = await _dnsWebService.DnsServer.DnsApplicationManager.UpdateApplicationAsync(name, fS);
|
||||
|
||||
_dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was updated successfully.");
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), "[" + session.User.Username + "] DNS application '" + name + "' was updated successfully.");
|
||||
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
|
||||
jsonWriter.WritePropertyName("updatedApp");
|
||||
WriteAppAsJson(jsonWriter, application, null);
|
||||
WriteAppAsJson(jsonWriter, application);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -637,76 +607,70 @@ namespace DnsServerCore
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dnsWebService.Log.Write(ex);
|
||||
_dnsWebService._log.Write(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UninstallApp(HttpListenerRequest request)
|
||||
public void UninstallApp(HttpContext context)
|
||||
{
|
||||
string name = request.QueryString["name"];
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new DnsWebServiceException("Parameter 'name' missing.");
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
name = name.Trim();
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
string name = request.GetQueryOrForm("name").Trim();
|
||||
|
||||
_dnsWebService.DnsServer.DnsApplicationManager.UninstallApplication(name);
|
||||
_dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was uninstalled successfully.");
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), "[" + session.User.Username + "] DNS application '" + name + "' was uninstalled successfully.");
|
||||
}
|
||||
|
||||
public async Task GetAppConfigAsync(HttpListenerRequest request, JsonTextWriter jsonWriter)
|
||||
public async Task GetAppConfigAsync(HttpContext context)
|
||||
{
|
||||
string name = request.QueryString["name"];
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new DnsWebServiceException("Parameter 'name' missing.");
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
name = name.Trim();
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.View))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
string name = request.GetQueryOrForm("name").Trim();
|
||||
|
||||
if (!_dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application))
|
||||
throw new DnsWebServiceException("DNS application was not found: " + name);
|
||||
|
||||
string config = await application.GetConfigAsync();
|
||||
|
||||
jsonWriter.WritePropertyName("config");
|
||||
jsonWriter.WriteValue(config);
|
||||
Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
|
||||
jsonWriter.WriteString("config", config);
|
||||
}
|
||||
|
||||
public async Task SetAppConfigAsync(HttpListenerRequest request)
|
||||
public async Task SetAppConfigAsync(HttpContext context)
|
||||
{
|
||||
string name = request.QueryString["name"];
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new DnsWebServiceException("Parameter 'name' missing.");
|
||||
UserSession session = context.GetCurrentSession();
|
||||
|
||||
name = name.Trim();
|
||||
if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Modify))
|
||||
throw new DnsWebServiceException("Access was denied.");
|
||||
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
string name = request.GetQueryOrForm("name").Trim();
|
||||
|
||||
if (!_dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application))
|
||||
throw new DnsWebServiceException("DNS application was not found: " + name);
|
||||
|
||||
string formRequest;
|
||||
using (StreamReader sR = new StreamReader(request.InputStream, request.ContentEncoding))
|
||||
{
|
||||
formRequest = await sR.ReadToEndAsync();
|
||||
}
|
||||
string config = request.QueryOrForm("config");
|
||||
if (config is null)
|
||||
throw new DnsWebServiceException("Parameter 'config' missing.");
|
||||
|
||||
string[] formParts = formRequest.Split('&');
|
||||
if (config.Length == 0)
|
||||
config = null;
|
||||
|
||||
foreach (string formPart in formParts)
|
||||
{
|
||||
if (formPart.StartsWith("config="))
|
||||
{
|
||||
string config = Uri.UnescapeDataString(formPart.Substring(7));
|
||||
await application.SetConfigAsync(config);
|
||||
|
||||
if (config.Length == 0)
|
||||
config = null;
|
||||
|
||||
await application.SetConfigAsync(config);
|
||||
|
||||
_dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' app config was saved successfully.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new DnsWebServiceException("Missing POST parameter: config");
|
||||
_dnsWebService._log.Write(context.GetRemoteEndPoint(), "[" + session.User.Username + "] DNS application '" + name + "' app config was saved successfully.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user