A production-ready GeoDNS policy for PowerDNS Authoritative Server, built with Lua, MaxMind GeoLite2, and EDNS Client Subnet-aware routing logic.
It returns different DNS answers based on the geographic signal available at query time:
regional clients/resolvers -> regional endpoint
external clients/resolvers -> external endpoint
The default policy is configured for Iran as the regional country:
IR -> regional endpoint
non-IR -> external endpoint
The implementation uses neutral regional and external naming so the same pattern can be adapted to other countries or private regional-routing designs.
This repository provides a reusable PowerDNS GeoDNS policy layer for two-endpoint traffic steering. It is intended for authoritative DNS deployments where a domain should resolve to one endpoint for a target region and another endpoint for the rest of the world.
The policy combines:
- PowerDNS Lua records
- MaxMind GeoLite2 country lookup
- EDNS Client Subnet-aware routing
- Resolver GeoIP fallback
- Manual resolver override rules
- Safe fallback behavior for unknown or incomplete signals
Minimum PowerDNS requirement:
PowerDNS Authoritative Server 4.2+
Recommended production baseline:
PowerDNS Authoritative Server 4.9+ or 5.x
Check your installed version:
pdns_server --versionThe main function is:
geo_pick(regional_ip, external_ip [, default_side])Example:
geo_pick("192.0.2.10", "198.51.100.10")With the default regional country set to IR, the expected behavior is:
Iranian client or resolver -> 192.0.2.10
Non-Iranian client or resolver -> 198.51.100.10
Unknown signal -> fallback side
The example IP addresses are documentation ranges. Replace them with your own endpoints in your deployment configuration.
The policy evaluates multiple signals in a predictable order:
- Manual resolver override
- Trusted EDNS Client Subnet signal
- Resolver GeoIP country
- PowerDNS
bestwhofallback - Configured default side
This helps avoid relying on a single signal. For example, if ECS is unavailable or not trusted, the policy can still fall back to resolver GeoIP or the configured default side.
.
├── lua-global/
│ └── 10-geo-policy.lua
├── zones/
│ └── examples/
│ └── example.com.yaml
├── docs/
│ ├── INSTALL.md
│ ├── TESTING.md
│ ├── DEPLOYMENT_CHECKLIST.md
│ ├── RELEASE_NOTES_v1.0.0.md
│ ├── pdns.conf.example
│ └── geoip-backend.yaml.example
├── scripts/
│ └── validate.sh
├── .github/
│ └── workflows/
│ └── validate.yml
├── Makefile
├── SECURITY.md
├── LICENSE
└── README.md
- Linux server
- PowerDNS Authoritative Server 4.2+
- Recommended: PowerDNS Authoritative Server 4.9+ or 5.x
- PowerDNS GeoIP backend
- Lua records enabled
- MaxMind GeoLite2 Country database
geoipupdatefor database updatesdigfor DNS validation
Clone the repository:
git clone https://github.com/homfar/powerdns-geodns.git
cd powerdns-geodnsRun repository validation:
bash scripts/validate.shOr:
make validateInstall PowerDNS Authoritative Server, the GeoIP backend, DNS tools, Lua, and MaxMind GeoIP update tools.
Debian/Ubuntu example:
sudo apt update
sudo apt install -y pdns-server pdns-backend-geoip dnsutils lua5.4 geoipupdateCheck the installed PowerDNS version:
pdns_server --versionCreate the required directories:
sudo mkdir -p /etc/powerdns/lua
sudo mkdir -p /etc/powerdns/geoip
sudo mkdir -p /etc/powerdns/zones
sudo mkdir -p /usr/share/GeoIPCopy the Lua policy file:
sudo cp lua-global/10-geo-policy.lua /etc/powerdns/lua/10-geo-policy.luaThis project expects the GeoLite2 Country database at the following recommended production path:
/usr/share/GeoIP/GeoLite2-Country.mmdb
Configure MaxMind GeoIP Update:
sudo nano /etc/GeoIP.confExample /etc/GeoIP.conf:
AccountID YOUR_MAXMIND_ACCOUNT_ID
LicenseKey YOUR_MAXMIND_LICENSE_KEY
EditionIDs GeoLite2-Country
DatabaseDirectory /usr/share/GeoIPRun the database update:
sudo geoipupdate -vVerify that the database exists:
ls -lh /usr/share/GeoIP/GeoLite2-Country.mmdbRecommended permissions:
sudo chown root:root /usr/share/GeoIP/GeoLite2-Country.mmdb
sudo chmod 0644 /usr/share/GeoIP/GeoLite2-Country.mmdbIf your operating system stores MaxMind databases in another directory, keep the same path in your PowerDNS GeoIP configuration.
Create or edit the PowerDNS GeoDNS configuration:
sudo nano /etc/powerdns/pdns.d/geodns.confRecommended configuration:
launch=geoip
geoip-database-files=/usr/share/GeoIP/GeoLite2-Country.mmdb
geoip-zones-file=/etc/powerdns/geoip/geoip-backend.yamlIf your PowerDNS server already uses another backend, do not remove it. Include the GeoIP backend in the existing backend list, for example:
launch=gsqlite3,geoipor:
launch=bind,geoipThe important point is that the geoip backend must be loaded and the geoip-database-files path must point to the actual .mmdb file.
Copy the example GeoIP backend configuration and adjust it for your own domain and paths:
sudo cp docs/geoip-backend.yaml.example /etc/powerdns/geoip/geoip-backend.yaml
sudo nano /etc/powerdns/geoip/geoip-backend.yamlCopy the example zone file and customize the domain, records, and endpoint IPs:
sudo cp zones/examples/example.com.yaml /etc/powerdns/zones/example.com.yaml
sudo nano /etc/powerdns/zones/example.com.yamlReview the example files before applying them to a server:
cat docs/pdns.conf.example
cat docs/geoip-backend.yaml.example
cat zones/examples/example.com.yamlA Lua-backed A record can be defined like this:
records:
- name: "www"
type: "A"
ttl: 60
content: "geo_pick('192.0.2.10', '198.51.100.10')"Meaning:
192.0.2.10 -> regional endpoint
198.51.100.10 -> external endpoint
Recommended rollout approach:
- Start with a low TTL.
- Test from regional and external networks.
- Confirm resolver behavior.
- Increase TTL after verification.
After configuration changes, validate PowerDNS before restarting it:
sudo pdns_server --daemon=no --guardian=no --loglevel=9If the configuration loads correctly, stop the foreground process with Ctrl+C, then restart PowerDNS:
sudo systemctl restart pdns
sudo systemctl status pdns --no-pagerCheck recent logs:
sudo journalctl -u pdns -n 100 --no-pagerTest a local query:
dig @127.0.0.1 www.example.com A +shortTest against the authoritative DNS server IP:
dig @YOUR_AUTH_DNS_IP www.example.com A +shortTest with EDNS Client Subnet:
dig @YOUR_AUTH_DNS_IP www.example.com A +subnet=5.0.0.0/24A recommended rollout flow:
- Start with low TTL values.
- Test from regional and external networks.
- Test with multiple public resolvers.
- Check PowerDNS logs after each change.
- Confirm the returned records match the expected routing policy.
- Increase TTL values after the behavior is stable.
Detailed documentation:
The default regional country is currently IR.
A future version can move this into a single configurable value, for example:
REGIONAL_COUNTRY_CODE = "IR"This would make the policy easier to reuse for other countries without changing the decision logic.
Basic local query:
dig @127.0.0.1 www.example.com AQuery an authoritative DNS server:
dig @YOUR_AUTH_DNS_IP www.example.com A +shortTest with EDNS Client Subnet:
dig @YOUR_AUTH_DNS_IP www.example.com A +subnet=5.0.0.0/24Use the full testing guide for a complete validation flow:
docs/TESTING.md
- Keep
GEOPOLICY_DEBUG=falsein production. - Keep deployment-specific values in your private operational configuration.
- Use documentation ranges in examples and public templates.
- Start rollout with low TTL values.
- Test with multiple resolvers and networks.
- Keep the GeoLite2 database updated with
geoipupdate. - Review resolver override lists periodically.
- Monitor DNS answers after every policy or zone change.
- Validate GeoDNS behavior before enabling DNSSEC for affected zones.
Run:
bash scripts/validate.shOr:
make validateThe validation script checks the repository layout, required files, example configuration, and common packaging issues.
GitHub Actions runs validation on push and pull request.
This repository includes a basic Security Policy.
For public examples and templates, use documentation-only domains and IP ranges:
example.com
192.0.2.0/24
198.51.100.0/24
203.0.113.0/24
Keep environment-specific configuration, operational resolver lists, database files, and deployment secrets outside the repository.
Recommended first stable tag:
v1.0.0
Release notes:
docs/RELEASE_NOTES_v1.0.0.md
Possible improvements:
- Configurable regional country code
- Multi-region country-to-endpoint mapping
- Containerized PowerDNS integration tests
- Automated resolver classification helper
- DNS answer monitoring script
- Ansible role for repeatable deployment
این پروژه یک سیاست GeoDNS برای PowerDNS Authoritative Server است. منطق آن با Lua نوشته شده و برای تصمیمگیری از MaxMind GeoLite2، اطلاعات resolver و EDNS Client Subnet استفاده میکند.
هدف پروژه انتخاب پاسخ DNS بین دو مسیر است:
کاربر یا resolver منطقهای -> endpoint منطقهای
کاربر یا resolver خارجی -> endpoint خارجی
در تنظیم پیشفرض، کشور منطقهای ایران (IR) است:
IR -> مسیر منطقهای
غیر IR -> مسیر خارجی
نامگذاری پروژه بهصورت regional و external انجام شده تا بتوان از همین مدل برای سناریوهای مشابه نیز استفاده کرد.
- مسیریابی GeoDNS روی PowerDNS
- سیاست DNS مبتنی بر Lua
- تشخیص کشور با MaxMind GeoLite2
- پشتیبانی از EDNS Client Subnet
- override دستی برای resolverها
- fallback برای سیگنالهای ناقص یا نامشخص
- نمونه تنظیمات PowerDNS
- نمونه zone برای GeoIP backend
- مستندات نصب، تست و deployment
حداقل نسخه مورد نیاز:
PowerDNS Authoritative Server 4.2+
نسخه پیشنهادی برای production:
PowerDNS Authoritative Server 4.9+ یا 5.x
برای مشاهده نسخه نصبشده:
pdns_server --versionتابع اصلی:
geo_pick(regional_ip, external_ip [, default_side])نمونه:
geo_pick("192.0.2.10", "198.51.100.10")در حالت پیشفرض:
کاربر یا resolver ایرانی -> 192.0.2.10
کاربر یا resolver غیرایرانی -> 198.51.100.10
سیگنال نامشخص -> مسیر fallback
پروژه برای انتخاب پاسخ DNS این موارد را بررسی میکند:
- override دستی resolver
- EDNS Client Subnet در صورت قابل اعتماد بودن
- کشور resolver بر اساس GeoIP
- fallback مبتنی بر
bestwho - مسیر پیشفرض
این مدل باعث میشود تصمیمگیری فقط به یک سیگنال وابسته نباشد.
git clone https://github.com/homfar/powerdns-geodns.git
cd powerdns-geodns
bash scripts/validate.shنمونه نصب روی Debian/Ubuntu:
sudo apt update
sudo apt install -y pdns-server pdns-backend-geoip dnsutils lua5.4 geoipupdateنسخه PowerDNS را بررسی کنید:
pdns_server --versionدایرکتوریهای لازم را بسازید:
sudo mkdir -p /etc/powerdns/lua
sudo mkdir -p /etc/powerdns/geoip
sudo mkdir -p /etc/powerdns/zones
sudo mkdir -p /usr/share/GeoIPفایل Lua policy را کپی کنید:
sudo cp lua-global/10-geo-policy.lua /etc/powerdns/lua/10-geo-policy.luaمسیر پیشنهادی دیتابیس:
/usr/share/GeoIP/GeoLite2-Country.mmdb
فایل تنظیمات MaxMind GeoIP Update را ویرایش کنید:
sudo nano /etc/GeoIP.confنمونه تنظیمات:
AccountID YOUR_MAXMIND_ACCOUNT_ID
LicenseKey YOUR_MAXMIND_LICENSE_KEY
EditionIDs GeoLite2-Country
DatabaseDirectory /usr/share/GeoIPدیتابیس را دریافت یا بهروزرسانی کنید:
sudo geoipupdate -vوجود فایل دیتابیس را بررسی کنید:
ls -lh /usr/share/GeoIP/GeoLite2-Country.mmdbسطح دسترسی پیشنهادی:
sudo chown root:root /usr/share/GeoIP/GeoLite2-Country.mmdb
sudo chmod 0644 /usr/share/GeoIP/GeoLite2-Country.mmdbاگر سیستمعامل دیتابیس را در مسیر دیگری قرار میدهد، همان مسیر باید در تنظیمات PowerDNS استفاده شود.
فایل تنظیمات GeoDNS را بسازید یا ویرایش کنید:
sudo nano /etc/powerdns/pdns.d/geodns.confتنظیمات پیشنهادی:
launch=geoip
geoip-database-files=/usr/share/GeoIP/GeoLite2-Country.mmdb
geoip-zones-file=/etc/powerdns/geoip/geoip-backend.yamlاگر PowerDNS شما از backend دیگری هم استفاده میکند، backend قبلی را حذف نکنید. geoip را به لیست backendها اضافه کنید. مثال:
launch=gsqlite3,geoipیا:
launch=bind,geoipنکته اصلی این است که backend با نام geoip فعال باشد و مسیر geoip-database-files به فایل واقعی .mmdb اشاره کند.
فایلهای نمونه را برای محیط خود کپی و ویرایش کنید:
sudo cp docs/geoip-backend.yaml.example /etc/powerdns/geoip/geoip-backend.yaml
sudo nano /etc/powerdns/geoip/geoip-backend.yaml
sudo cp zones/examples/example.com.yaml /etc/powerdns/zones/example.com.yaml
sudo nano /etc/powerdns/zones/example.com.yamlقبل از restart، تنظیمات PowerDNS را بررسی کنید:
sudo pdns_server --daemon=no --guardian=no --loglevel=9اگر تنظیمات بدون خطا load شد، با Ctrl+C خارج شوید و سرویس را restart کنید:
sudo systemctl restart pdns
sudo systemctl status pdns --no-pagerلاگها را بررسی کنید:
sudo journalctl -u pdns -n 100 --no-pagerتست ساده:
dig @127.0.0.1 www.example.com A +shortتست روی authoritative DNS:
dig @YOUR_AUTH_DNS_IP www.example.com A +shortتست با EDNS Client Subnet:
dig @YOUR_AUTH_DNS_IP www.example.com A +subnet=5.0.0.0/24مستندات کاملتر:
docs/INSTALL.md
docs/TESTING.md
docs/DEPLOYMENT_CHECKLIST.md
- مقدار
GEOPOLICY_DEBUGدر production برابرfalseباشد. - مقدارهای مخصوص deployment را در تنظیمات عملیاتی خصوصی نگه دارید.
- برای مثالهای عمومی از IPهای مستنداتی استفاده کنید.
- rollout اولیه را با TTL پایین انجام دهید.
- رفتار DNS را از چند resolver و چند شبکه تست کنید.
- دیتابیس GeoLite2 را با
geoipupdateبهروزرسانی کنید. - بعد از هر تغییر، پاسخهای DNS و لاگهای PowerDNS را بررسی کنید.
MIT License