diff --git a/README.md b/README.md index 5017755..9b84aaa 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,14 @@ Read [Dohna NS Documentation](https://dohna.ovh/) to learn how to install Dohna ## Environment Variables -| Key | Default | Description | -| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | -| DNS | ["https://8.8.8.8/dns-query","https://8.8.4.4/dns-query","https://[2001:4860:4860::8888]/dns-query","https://[2001:4860:4860::8888]/dns-query"] | Specify a DNS over HTTPS server as the upstream. | -| API | ["https://8.8.8.8/resolve","https://8.8.4.4/resolve","https://[2001:4860:4860::8888]/resolve","https://[2001:4860:4860::8888]/resolve"] | Specify a JSON API server as the upstream. | -| IPV4_PREFIX | 32 | Specify the EDNS client subnet IPv4 prefix length. | -| IPV6_PREFIX | 128 | Specify the EDNS client subnet IPv6 prefix length. | -| CONCURRENT | false | Whether it concurrently queries all servers and returns the fastest result. | +| Key | Default | Description | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| DNS | ["https://8.8.8.8/dns-query","https://8.8.4.4/dns-query","https://[2001:4860:4860::8888]/dns-query","https://[2001:4860:4860::8888]/dns-query"] | Specify a DNS over HTTPS server as the upstream. | +| API | ["https://8.8.8.8/resolve","https://8.8.4.4/resolve","https://[2001:4860:4860::8888]/resolve","https://[2001:4860:4860::8888]/resolve"] | Specify a JSON API server as the upstream. | +| IPV4_PREFIX | 32 | Specify the EDNS client subnet IPv4 prefix length. | +| IPV6_PREFIX | 128 | Specify the EDNS client subnet IPv6 prefix length. | +| CONCURRENT | false | Whether it concurrently queries all servers and returns the fastest result. | +| ENABLE_MOBILECONFIG | false | Whether to enable the [Apple MobileConfig API](#apple-mobileconfig-api). | ## Self-hosted @@ -36,3 +37,15 @@ You can use [Netlify CLI](https://cli.netlify.com/commands/serve/) or [`workerd` Make sure you can connect to upstream servers. If you find a bug on self-hosted, try to reproduce it at `dohna.ovh` before reporting it. + +## Apple MobileConfig API + +MobileConfig can configure system-level DNS over HTTPS for Apple devices. + +`dohna.ovh` has already enabled this API; you only need to edit a few parameters to point the URL to your domain, so there is no need to enable this API for your self-hosted instance. + +| Query Parameter | Default | Description | +| --------------- | --------------------------------- | ------------------------------------------------------ | +| domain | dohna.ovh | Specify the DNS over HTTPS domain. | +| name | Dohna NS | Specify the name of the generated MobileConfig. | +| desc | Yet another DNS over HTTPS relay. | Specify the description of the generated MobileConfig. | diff --git a/_worker.js b/_worker.js index 2f356df..f48339e 100644 --- a/_worker.js +++ b/_worker.js @@ -10,5 +10,6 @@ export default { env.IPV6_PREFIX, env.CONCURRENT, request.headers.get("cf-connecting-ip"), + env.ENABLE_MOBILE_CONFIG, ), }; diff --git a/handler/dns.js b/handler/dns.js index f87e33c..7f75ddd 100644 --- a/handler/dns.js +++ b/handler/dns.js @@ -118,28 +118,28 @@ export default async function handler( if (method === "POST") { const requestBody = await request.arrayBuffer(); - // Anti-GFW + // Anti-GFW + if ( + headers.get("content-length") === "29" && + (headers.get("user-agent") === "Go-http-client/1.1" || + headers.get("user-agent") === "Go-http-client/2.0") && + headers.get("accept") === "application/dns-message" && + headers.get("content-type") === "application/dns-message" && + (headers.get("accept-encoding") === "gzip, br" || + headers.get("accept-encoding") === "gzip") + ) { + const bodyHex = Array.from(new Uint8Array(requestBody)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); if ( - headers.get("content-length") === "29" && - (headers.get("user-agent") === "Go-http-client/1.1" || - headers.get("user-agent") === "Go-http-client/2.0") && - headers.get("accept") === "application/dns-message" && - headers.get("content-type") === "application/dns-message" && - (headers.get("accept-encoding") === "gzip, br" || - headers.get("accept-encoding") === "gzip") + bodyHex.slice(4) === + "01100001000000000000077477697474657203636f6d0000010001" ) { - const bodyHex = Array.from(new Uint8Array(requestBody)) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); - if ( - bodyHex.slice(4) === - "01100001000000000000077477697474657203636f6d0000010001" - ) { - return new Response(null, { status: 403 }); - } + return new Response(null, { status: 403 }); } - queryData = new Uint8Array(requestBody); } + queryData = new Uint8Array(requestBody); + } if (queryData) { res = await queryDns( diff --git a/handler/entrypoint.js b/handler/entrypoint.js index fb81ccb..bda845f 100644 --- a/handler/entrypoint.js +++ b/handler/entrypoint.js @@ -1,19 +1,36 @@ import dnsHandler from "./dns"; +import mobileconfigHandler from "./mobileconfig"; export default async function handler( - request, - dns, - api, - ipv4Prefix = 32, - ipv6Prefix = 128, - concurrent = false, - rawIP, - ) { - const { pathname } = new URL(request.url); - let res = new Response(null, { status: 404 }); + request, + dns, + api, + ipv4Prefix = 32, + ipv6Prefix = 128, + concurrent = false, + rawIP, + enableMobileConfig = false, +) { + const { pathname } = new URL(request.url); + let res = new Response(null, { status: 404 }); - // DNS over HTTPS & JSON API - if (pathname === "/dns-query" || pathname === "/resolve") { - res = dnsHandler(request, dns, api, ipv4Prefix, ipv6Prefix, concurrent, rawIP); - } -} \ No newline at end of file + // DNS over HTTPS & JSON API + if (pathname === "/dns-query" || pathname === "/resolve") { + res = dnsHandler( + request, + dns, + api, + ipv4Prefix, + ipv6Prefix, + concurrent, + rawIP, + ); + } + + // Apple Mobile Config + if (enableMobileConfig && pathname === "/mobileconfig") { + res = mobileconfigHandler(request); + } + + return res; +} diff --git a/handler/mobileconfig.js b/handler/mobileconfig.js new file mode 100644 index 0000000..e898129 --- /dev/null +++ b/handler/mobileconfig.js @@ -0,0 +1,61 @@ +export default async function handler(request) { + const { headers, searchParams } = new URL(request.url); + const domain = searchParams.get("domain") || headers.get("domain") || "dohna.ovh"; + const name = decodeURIComponent(searchParams.get("name")) || "Dohna NS"; + const desc = + decodeURIComponent(searchParams.get("desc")) || + "Yet another DNS over HTTPS relay."; + return new Response( + ` + + + +PayloadContent + + +DNSSettings + +DNSProtocol +HTTPS +ServerURL +https://${domain}/dns-query + +PayloadDisplayName +${name} +PayloadDescription +${desc} +PayloadIdentifier +com.apple.dnsSettings.managed.${String(Crypto.randomUUID()).toUpperCase()} +PayloadType +com.apple.dnsSettings.managed +PayloadUUID +${String(Crypto.randomUUID()).toUpperCase()} +PayloadVersion +1 +ProhibitDisablement + + + +PayloadDisplayName +${name} +PayloadDescription +${desc} +PayloadIdentifier +${domain} +PayloadRemovalDisallowed + +PayloadType +Configuration +PayloadUUID +${String(Crypto.randomUUID()).toUpperCase()} +PayloadVersion +1 + +`, + { + headers: { + "Content-Type": "application/x-apple-aspen-config", + }, + }, + ); +} diff --git a/middleware.js b/middleware.js index bbf12cf..b41a061 100644 --- a/middleware.js +++ b/middleware.js @@ -8,5 +8,7 @@ export default middleware = async (request) => { process.env.IPV4_PREFIX, process.env.IPV6_PREFIX, process.env.CONCURRENT, + undefined, + process.env.ENABLE_MOBILE_CONFIG, ); }; diff --git a/netlify/edge-functions/middleware.js b/netlify/edge-functions/middleware.js index 5b4fb8d..98343f5 100644 --- a/netlify/edge-functions/middleware.js +++ b/netlify/edge-functions/middleware.js @@ -8,5 +8,6 @@ export default async (request) => Netlify.env.get("IPV6_PREFIX"), Netlify.env.get("CONCURRENT"), Netlify.context.ip, + Netlify.env.get("ENABLE_MOBILE_CONFIG"), ); export const config = { path: "*" };