Arkanis Development

Styles

Build your own DynDNS

Published

During the last few weeks I wrote MiniDynDNS to build my own dynamic DNS service. Something like DynDNS but all by myself. This post explains the basic steps needed to wire MiniDynDNS into the worldwide DNS system.

I'm using it to create DNS names that point to devices at home I want to access via the internet. This is pretty nice with IPv6 since every device gets its own public IPv6 address. But please make sure only the services you want to have available are actually listening on the public IPv6 address. Or configure your firewalls accordingly.

To build your own DynDNS you'll need a few bits and pieces:

The bigger picture

The whole idea of this operation is to create a subdomain that is managed by a program running on your server. Here we'll use dyn.example.com but it can be anything as long as it's a subdomain of your registered domain. Whenever someone on the world resolves a name like weather.dyn.example.com they're going to ask that program on your server to get the current IP of that name.

For that we first need a program running on your server that can answer DNS requests and allows us to update these IPs when they change. Obviously we're going to use MiniDynDNS for that. That's why I wrote it.

Second we need to tell the global DNS system that the program running on your server is responsible ("authoritative") for dyn.example.com subdomain. This is called "delegating" a subdomain. When you bought your own domain you also bought the right to delegate subdomains to whoever you deem worthy. With that in place whenever someone resolves a name in dyn.example.com they'll ask MiniDynDNS on your server.

Note that you can only delegate a subdomain to a host, e.g. ns.example.com. This host then has to resolve to 203.0.113.17. You can delegate to whatever host you want but in the end this host has to resolve to your public IP. Here we'll use ns.example.com as a placeholder for that.

The final part is a script running on whatever device or computer you want to have a dynamic domain name for. That script will periodically report its current IP to your MiniDynDNS.

If everything works correctly you can add any devices you want to your dyn.example.com subdomain and access them from everywhere on the world. pi.dyn.example.com, weather-station.dyn.example.com, tv.dyn.example.com, touchtable.dyn.example.com, spidercam.dyn.example.com or whatever. Get creative.

So lets get to it.

1. Run MiniDynDNS on your server

Download or clone MiniDynDNS from GitHub and do the "installation". Basically that's renaming config.example.yml to config.yml and setting the proper values for your setup. The domain, soanameserver and soamail are the important ones.

For MiniDynDNS to answer incoming DNS requests it has to listen on port 53. That's where other servers or clients expect to get DNS requests answered. Changing that will probably break things.

Per default it will use port 80 for a simple HTTP API with which we can update DNS records. In case this port is already used by a webserver like Apache you can change it to something else like 8080. We only need it for the scripts that periodically report the IPs of your devices to the server.

You can tell your server system to start MiniDynDNS on server startup. For me it's just a funny hobby so I leave it running in a screen terminal. You might also need to tell your servers firewall to open port 53 and 80 for incoming traffic (or whatever port you use for the HTTP interface). Otherwise the firewall will block everything and you'll just get timeouts.

Now your basic DynDNS server should already be up and running. To test it you can fire up a terminal and try this command:

nslookup foo.dyn.example.com 203.0.113.17

This tells nslookup to resolve foo.dyn.example.com by asking the DNS server 203.0.113.17. If everything works well it should tell you that foo.dyn.example.com has the IP address 192.168.0.1.

This assumes that you use the default database (just renamed db.example.yml to db.yml). If you already changed your DB you have to change the domain name in the command accordingly.

2. Delegate dyn.example.com to the MiniDynDNS server

Now on to tell the rest of the world that your server manages the dyn.example.com by itself. For this you have to add two records to your normal nameserver. In my case the company where I registered my domain provides a webinterface for this task.

You have to add these two DNS records to your example.com nameserver:

dyn  NS  ns.example.com
ns   A   203.0.113.17

The first record tells the DNS system that the dyn.example.com subdomain is delegated to ns.example.com. The second record says that ns.example.com has the IPv4 address 203.0.113.17. Please remember to replace the domain name and the IP with your own values. If your server also has an IPv6 address you should add an AAAA record for that, too.

3. Update your IPs periodically

This differs greatly between IPv4 and IPv6.

With IPv4 only your router has a public IP address. For every service you have to create a port forwarding to the appropriate internal computer or device. So in any way there's only one public IP to report to the DNS server. Many routers already have build-in support for this. Usually it's hidden somewhere in the webinterface called "dynamic DNS", "DynDNS" or something like that.

In the case of my FritzBox router I have to enter the domain (foo.dyn.example.com), a user name (just foo), the password (bar) and an update URL (http://ns.example.com/?myip=<ipaddr>). The router will replace "<ipaddr>" with its current public IPv4 address. It seems like it then just fires this HTTP request every 30 minutes. Again these values are based on the default db.yml file.

The required steps are probably quite different for other routers. You might even have to look into the manual or search a bit to figure out how to do this.

With IPv6 the situation is a bit simpler. Each device gets its own public IPv6 address. A script that runs every few minutes can simply report that IP to the DNS server. In case of a RaspberryPi running Raspbian this script will do the job:

IP=$( \
    ip addr show dev eth0 scope global | \
    grep --perl-regexp --only-matching '(?<=inet6 )2003:[0-9a-f:]+' | \
    head --lines 1 \
)
curl -s http://foo:bar@ns.example.com/?myip=$IP

Again, "foo", "bar" and "ns.example.com" are values from the default db.yml. Replace them with the values for your setup. In case you changed the port of the webinterface you also have to add a port to the HTTP request (something like http://foo:bar@ns.example.com:8080/?myip=$IP for port 8080).

Save it to /home/pi/update_ip.sh and make it executable with chmod u+x update_ip.sh. When you run it (./update-ip.sh) you should see something like "Your IP has been updated".

To execute the script every 5 minutes you just need to add a line to your crontab. The command crontab -e should show you an text editor and by adding this line at the end you should be set:

*/5  *  *  *  *  /home/pi/update_ip.sh 2>&1 >> /home/pi/update_ip.log

The "*/5" at the beginning means "every 5 minutes". If you want to run the script every 30 minutes its "*/30".

Done!

Phew, this was more text than I expected. When you run the command nslookup foo.dyn.example.com you should now see 192.168.0.1 as a result (again, default database values). Note that this command asks the nameserver provided by your environment (e.g. by your ISP). Thanks to the domain delegation this DNS request should end up at your nameserver which can then answer it. When you have a webserver running on one of your devices you can even use these domain names to access websites.

Anyway, with that setup you should be able to manage your own subdomains. The MiniDynDNS readme has some more information and useful commands so better take a look there, too.

Have fun with MiniDynDNS. :)

48 comments for this post

leave a new one

#1 by
Weeber

I haven't implemented this yet but it looks like it's going to be very useful. Thanks!

#2 by
heru.f

2016-09-20 09:19:08 SERVER: Running DNS on 0.0.0.0:53, HTTP on 0.0.0.0:80, as user nobody:nogroup 2016-09-20 09:19:18 HTTP: GET /?myip=192.168.5.10 -> not authorized 2016-09-20 09:19:18 HTTP: Failed to process request: Permission denied @ rb_sysopen - db.yml dns.rb:86:in `write' dns.rb:86:in `save_db' dns.rb:457:in `block in handle_http_connection' dns.rb:427:in `catch' dns.rb:427:in `handle_http_connection' dns.rb:582:in `block in <main>' dns.rb:568:in `loop'

how to resolve this…

#3 by
Stephan

Looks like the server can't access the db.ymlfile. Make sure that it exists and that the server has access to it. Per default the server loads the db.yml file from the current working directory. You can specify the path with the --db command line option.

Hope that helps.

#4 by
windows10times

That's fabulous. Came across various issues till date with the DNS and yup they are no more. Cool Post.

#5 by
Chris

Seems that minidyndns will not resolve itself:

DNS: A dyn.example.com -> wrong domain, ignoring

How to set an A record for "@"?

Thanks, Chris

#6 by
Stephan

Hi Chris,

never thought about that use case actually. The server had a check in place that it only resolves subdomains and nothing else. I loosened that check a bit and added the "@" name to represent the server itself. I pushed it online as version 1.1.1.

To define an IP address for the server itself you just have to add a record for "@" in the YAML database. Something like that:

"@":
  pass:
  A: 192.168.0.4
  AAAA: ff80::4

Since it's YAML you have to quote the @ sign. It's a normal record so you can even change it via the HTTP/HTTPS interface by using "@" as a username. I'm not sure this is a good idea so it's probably best to disable that by setting an empty password (makes the record unchangable via the web interface).

I hope this does what you need. :)

ps.: You just have to download the new dns.rb file. Nothing else changed.

#7 by
Fernando

Hi there, thanks a lot for writting this guide. Have you ever though on writting this but for Synlogy DSM OS?

This would be something great to have running on my NAS :)

Thanks again

Fernando

#8 by
Stephan

Hi Fernando,

thanks. Hm, never heard of Synlogy DSM OS but it looks quite nice. Maybe their SDK supports Ruby. In that case you could create a package for it. Unfortunately I don't have a NAS so I can't help there. :(

#9 by
SebiTNT

Hi there, at first: Thank you for this great tool and howto! I have a few questions and a little fix to share. I ran into some difficulties adding my letsencrypt certificates to use with this dyndns server. The problem was, that the letsencrypt script queried my servername "dyn.example.com" with some strange uppercase/lowercase mix, while it was creating my certificates. After some searching I found out, that this behaviour is called "0x20 Bit encoding" and is for some safety purposes. Because the server was configured to reply only to the lowercase version, the certificate creation was aborted, because the server didn't reply to DyN.exAMPlE.com in example. Thus lead me to adding .downcase in line "if domain.downcase == $config["domain"] and (type == TYPE_SOA or type == TYPE_ALL)" and in line "log "DNS: #{type_as_string} #{domain} -> wrong domain, ignoring" and return nil unless domain.downcase.end_with?($config["domain"])" These changes fixed it for me and my certificate was created correctly and is now in use with this dyndns-server. Because I'm not familiar with ruby and dns, please let me know, if this was a bad idea or not.

One question I have to ask is, if there is a chance of disabling the non-https-server for only allowing https connections. Is there any chance to do this or is the http essential for this to work?

Sebi

#10 by
Stephan

Hi SebiTNT,

thanks for the feedback and the fix! Especially the "0x20 Bit encoding" thing was very helpful. It made me realize that DNS names are actually case insensitive. Sounds stupid but I didn't realize that until now. Your fix doesn't just apply to the SOA record but to all records.

So I did a bit of reading and changed the server to case insensitive. Maybe that will fix some other elusive bugs, too (I'm looking at you, FritzBoxes). While I was at it the server also got the option to disable the HTTP interface. Just set the entire "http" key to "false" in your config. Same as with HTTPS. You can even disable both HTTP and HTTPS if you want.

So to answer your question: No the HTTP server isn't essential. I just didn't thought about adding a configuration option to disable it.

I tested and commited the stuff and put it online as release v1.1.2. Feel free to give it a try. :) You'll just have to download the new dns.rb file and you're done.

Thanks again for taking the time to look into this and sharing you fix. Comments like this make maintenance actually quite pleasent. And that's worth a lot to me. :)

#11 by
SebiTNT

Hi Stephan,

thank you very much for your kind reply and an instant implementation!

I've also seen another issue after sending a dns request to the server via my FritzBox. I get entries like "DNS: @ -> no records returned" in the log, especially if the request comes from my FritzBox. Note that there is a space character before the @. Other "normal" @-Queries are answered correctly. Do you know what this kind of query is for and how it can be reproduced without a FritzBox? Should there be a fix in your server tool?

Kind regards Sebi

#12 by
Stephan

Hi Sebi,

Now that's an interesting hint. And a strange log entry. Usually there should be a DNS record type before the name. Something like "A", "AAAA", "ANY" or "???". Since that part is missing I would guess that the server got a query for a DNS record type it doesn't understand. We better find out what record type the FritzBox asked for.

Hm, I looked into the code and seems like the "???" case didn't work. Not that is was a terribly useful. Fixed that and the server now logs unknown DNS record types by their decimal type (e.g. "5" for CNAME). Reason for that is that the server only prints types by name that it actually supports (e.g. "AAAA"). And I don't want to imply via the log messages that you can add CNAME or other unimplemented records to your db.yml file. Given the record type we also should be able to reproduce the query with tools like nslookup or dig.

Anyway, tested the changes and pushed that online as v1.1.3. Again you just have to download the new dns.rb file. After that the server will show something like the following for queries with unknown DNS record types:

DNS: type(5) foo -> no records returned

The server received a question for the DNS record type 5 (CNAME) of the subdomain "foo". Here's a nice list of the different record type and their decimal values: https://en.wikipedia.org/wiki/List_of_DNS_record_types

Let me know what record type gets requested. Then we can figure out if there's a new record type that needs implementation. :)

Happy programming Stephan

#13 by
SebiTNT

Hi Stephan,

thank you again so much!!! I have just tried it and it is asking for the NS record (decimal type 2). I've seen the same request from 194.150.168.168 and 213.73.91.35 (recommended dns-servers from the CCC website). So maybe an answer to this type of request should be added?

I really like your ddns-server because it is very compact and simple to understand!

Sebi

#14 by
Stephan

Hi Sebi,

If other servers ask MiniDynDNS for an NS record something about the setup is probably amiss. Except you actually want to create a nested hierarchy of dynamic DNS servers.

Usually the NS record should be provided by the nameserver of the company you rented your domain from. MiniDynDNS should only answer queries for the subdomain you delegated to it or subdomains of that subdomain, and so on.

For example when you rented the domain example.com from Domain Factory their DNS servers should provide the following records:

            A   <IP the webserver for example.com runs on>
www         A   <IP the webserver for www.example.com runs on>
minidyndns  A   203.0.113.17
dyn         NS  minidyndns.example.com

Note that you have to edit all those records in the webinterface of e.g. Domain Factory. Those records are under their authority and they operate the DNS servers that answer those queries. Your MiniDynDNS should never see any queries about those records.

The important record is the last one: "dyn NS minidyndns.example.com". This tells the world wide DNS system that the subdomain dyn.example.com is under the authority of the DNS server running on minidyndns.example.com. All queries regarding dyn.example.com or its subdomains should be directed there.

Now to ask minidyndns.example.com any questions we need to know its IP address. And that's what the "minidyndns A 203.0.113.17" is for. It tells anyone the IP they have to ask regarding questions about dyn.example.com and its subdomains. All this assumes of course that you run MiniDynDNS on a server with the IP 203.0.113.17. So change the IPs and names according to your setup. :)

To make it short: The NS record delegates the authority to minidyndns.example.com and the A record above tells us the IP of that lucky one. With that we can fire the UDP packet with the DNS query to the one that knows the answer.

Now on 203.0.113.17 there should run a MiniDynDNS that actually answers those queries. For example it can provide the following records:

    A  <IP the webserver for dyn.example.com runs on>
pi  A  <public IP of your router with port-forwardings to a Raspberry Pi>

The first record is for dyn.example.com and you would configure that with the "@" name in your db.yml. The second record is for pi.dyn.example.com.

Note that this in-between layer of dyn.example.com is necessary to establish the authority of your MiniDynDNS. With this kind of setup you can't for example host pi.example.com with your MiniDynDNS. Simply because subdomains of example.com are under the authority of company you rented your domain from. For those records you have to use the webinterface of that company.

Now there are 2 ways that might allow you to do it anyway:

1) Create pi.example.com as a CNAME record that redirects to pi.dyn.example.com. That way you can ask for pi.example.com but the DNS system will actually give you the IP of pi.dyn.example.com. You'll have to configure those records by hand via the webinterface of your domain provider. It would look like this: "pi CNAME pi.dyn.example.com". That's what I'm using right now.

2) You can take a look at DNAME records. Haven't used them yet but they might solve this. But I'm not sure about that.

That got a lot longer than expected. Sorry abut that. I hope the information helps. :)

Happy programming Stephan

#15 by
SebiTNT

Hi Stephan,

thank you for your long but very comprehensible answer!

The problem I see here is, that AFAIK dyn.example.com should know, which its responsible nameserver is (and ns.example.com [or minidyndns.example.com] should know the same). This is of course not essential for the basic functions, because normally it should go the other way around (com -> example -> dyn).

I did some tests on online nameserver test tools like http://dnscheck.ripe.net/ and they all said something like "No name servers found at child" and reported this as a fatal error. This is why I think a reply to a ns request should really be implemented, or do you have another opinion?

Have a nice Weekend! Sebi

#16 by
Stephan

Hi Sebi,

oh, I didn't know about that second function of NS records. Sorry about rumbling on about the basics before. :D

The server now answers NS queries about itself with a single NS record. It uses the same nameserver value as configured for the SOA record.

I could reproduce the "No name servers found at child" errors with RIPE DNSCheck. But even after implementing the NS answer it didn't work. The tool didn't even send a query to the server. Maybe the negative answer from before got cached so it might takes a day until I can check again with that tool.

On the other hand https://mxtoolbox.com/SuperTool.aspx did work quite well (even with some useful documentation!). At least my setup now passes the checks there. A warning about the SOA serial number format remains but I'm not really inclined to "fix" that. In the end it's just a number and gets incremented on every change.

I've pushed the changes online as version 1.1.4. Feel free to give it a try. :)

Happy programming Stephan

#17 by
Visitor

Hi, am i reading this right. We can set up our own Dyndns server to allow ourselves to notify ourselves that our dynamic IP has changed just like with NoIp and dyn DNS services?

Could this be done on a QNAP server using a virtual machine?

Regards Visitor

#18 by
Stephan

Not really. You need a server with a static IP to run MiniDynDNS on. Once you have that running devices like your QNAP can use that server like NoIp or others.

In effect you can setup your own dynamic IP service. But you need a proper server with a static IP for that. In the end the DNS needs an IP to ask for what a domain name means. And this IP has to be written down in a DNS record (see step 2 "Delegate dyn.example.com to the MiniDynDNS server" above).

In my case I have a vserver with a static IP address. So I use that to run MiniDynDNS on. Some of my home devices then use that MiniDynDNS to publish their IP addresses so I can access them.

Note that the server you run MiniDynDNS on doesn't have to be "in the internet" or in some server farm. As long as you have a machine with a static IP it'll work. In Germany a static IPv4 address will cost a bit so most people don't have one. In other countries this is different. So maybe you're lucky. Also with IPv6 you might get a static IP more easily or already have one. So you can use that static IPv6 address. But I haven't yet tried running MiniDynDNS via IPv6 only.

#19 by
Nixola

Hi! I'm trying to set up a dynamic DNS on a server using minidyndns, but I'm having issues with it. When I run nslookup I get a connection timeout and minidyndns prints

DNS: A foo.dyn.example.com -> wrong domain, ignoring

even though foo is set up in the database (note: not actually foo, and not actually example.com). Any idea why that might be?

Furthermore, I tried checking it with http://dnscheck.ripe.net/ and it says it can't find delegation or nameservers at parent, even though I set up the NS record as well…

#20 by
Stephan

Hi Nixola,

The "wrong domain" error occurs when the server receives a query for a domain that doesn't end in the value configured in the "domain" option. Maybe you forgot to restart the server after changing the configuration? Or maybe there is just a typo in the domain name (either in the query or in config.yml).

You can test this in isolation by sending a DNS query directly to your server. There are the dig and nslookup commands for a server running on 127.0.0.2 and configured for the domain dyn.foobar.com:

dig @127.0.0.2 abc.dyn.foobar.com A
nslookup abc.dyn.foobar.com 127.0.0.2

With those the server should reply with either an answer (if there is an IP for the name "abc") or "no records returned".

Regarding the RIPE DNS check: I'm not sure what happens there. My own setup also doesn't pass any tests there even though it works fine. The RIPE checker doesn't even send a packet to my server. I guess I'm still missing one last bit of magic in the domain delegation process but I can't figure out what that should be (I'm by no means an expert when it comes to DNS).

There are some other DNS checkers out there but most of them seem to be reskinned versions of the RIPE checker. The first genuine different tool that I stumbled upon (https://mxtoolbox.com/SuperTool.aspx) on the other hand shows all green (more or less). So I can only recommend to recheck your setup with that tool.

Happy programming Stephan

#21 by
Nixola

Nope, I keep getting the same issue (wrong domain, ignoring) pretty much regardless of what soa->nameserver is (I tried ns.example.com, dyn.example.com, example.com itself too). Both dig and nslookup return this after several tries (all ignored):

;; connection timed out; no servers could be reached
#22 by
Nixola

Ok, I'm a moron. I apparently forgot to change the FIRST config option (domain), I thought soa -> nameserver was it. (Honestly, though, the fact it's missing an empty line makes it kind of "buried" within the comments; I'd suggest adding an empty line in the example config file after it.)

#23 by
Stephan

Hi Nixola,

sorry to hear but I'm glad the solution was easy. :)

Thanks for mentioning the missing empty line. Without syntax highlighting it's very easy to miss the domain option in the example config file. I fixed that and put it online.

Happy programming Stephan

#24 by
Pablo

Hi Stephan. The DDNS is working great. How would you go about adding a log at the minidyndns server side. Currently it only shows the log after I stop the service with systemctl.

#25 by
Stephan

Hi Pablo,

the simplest solution would be to redirect the output into log files:

ruby dns.rb > access.log 2> error.log

But in production you're probably only interested in errors. So discarding the normal log output might be better:

ruby dns.rb > /dev/null 2> error.log

If you want to make sure the log file doesn't grow into infinity you can use logrotate. At least on Debian systems you just have to add a config file for it and tell it which log file to rename / compress / delete at what intervals.

In case you're logging the normal output (access.log above) you can also use "tail -f" to see what's going on right now.

Hope that helps Stephan

#26 by
enriluis

I'm now in near 2017. It looks like a possible solution for me: i need to make a ipsec tunnel vpn over tow pfsense box on my internal local area network(no internet access) whit milti-wan link each ..so almost solution speak about dyndns and other providers, them making may own dyndns it look like a possible solution for me. that's correct? correct me please

#27 by
Stephan

I'm not entirely sure I understand what you're trying to do. Here's what I understood: You have a local network with a VPN tunnel and no internet access from that network. If you want you can use MiniDynDNS in there as your DNS server. It should work just fine, haven't tested that though.

Hope that helps Stephan

#28 by
enriluis

Yes, Stephan that's right i have tow pfsense box, one in a network and other yin another network on another location linked by two dedicated(main and backup link) connections (insecure) for that reason i have vpn ipsec tunnel working today, so i need to make ipsec tunel over these redundant links or multi-wan , here is when dyndns are my possible solution because pfsense need dyndns to make the updates for interface record using hostname when main link goes down it update record and make possible the connection usin another interface as peer… sorry about my English, best regard

#29 by
enriluis

another question my own dyndns can by run on windows server?

#30 by
Kafui

very helpful post even in 2017 I managed to setup the service but my problem is when I directly ping my dyn subdomain that is pi.dyn.example.com. I get no results unless im using the server running minidyndns as dns server , please help

#31 by
Stephan

Sorry, I was quite busy with work so I haven't had time to answer the questions. So here we go. :)

@enriluis: From what I understood MiniDynDNS should work just fine in your case. As one link goes down the pfsense box should send an update to MiniDynDNS. From then on users should get the new IP for the other side.

You might want to look at the "ttl" value in the configuration file (config.yml). Per default it's set to 15, meaning that clients will cache the DNS responses for 15 seconds. When a link fails it will probably take those 15 seconds until clients discard their cached IPs and send new queries to MiniDynDNS. In your case you might want to lower that value to make the delay shorter. But be aware, this also results in more DNS queries send to MiniDynDNS which might cause performance problems (especially for long loading websites).

Regarding Windows server: I've never tested it. Theoretically MiniDynDNS only uses platform independent Ruby APIs so it should work. But realistically I would assume that there are some problems. Feel free to test it and come back to me with problems or bugs. Best open an issue for each bug you encounter: https://github.com/arkanis/minidyndns/issues.

@Kafui: Sounds like you still have to do step 2 of the blog post (delegate a domain to the MiniDynDNS server). As soon as the MiniDynDNS server runs you can send DNS queries directly to it and it will answer (as in your case). But when you ask other DNS servers on the world for records stored in the MiniDynDNS server they have to figure out how to contact the MiniDynDNS server. Thats what the delegation is for.

Also worth noting: example.com is reserved for testing. So it's probably blocked by world wide DNS servers. You have to use a normal domain you registered somewhere and for which you can edit the DNS records (again, see delegation).

In case you got all that working already: There is a strange bug with Fritzbox routers (popular in Germany). They discard IPv6 DNS queries to MiniDynDNS most of the time. Seems to be a strange bug with their internal DNS cache. If you're affected by that try IPv4 DNS queries (just ping an IPv4 address).

Hope thats helpful Stephan

#32 by
Billy HIl

Is this system ok for 50K-100K users?

#33 by
Stephan

I've never tested it with that much load and I wouldn't recommend using it without proper testing and benchmarking.

The DNS part itself is just an event loop that answers DNS packets as they arrive. That should scale up pretty well until one CPU core is saturated. But the HTTP part is just a blocking server running in an extra thread. In case there are many clients updating their IP it will collapse pretty quickly. HTTPS is even worse as there are still some weird bugs regarding OpenSSL and threadding in Rubys standard library.

In short: If you expect 50k-100k users on the DNS side, do a load test and then maybe consider it. If you expect that much users updating their IPs via HTTP or HTTPS don't even consider using it. Sorry. :(

#34 by
Geoffrey Callaghan

you can also use https://staticIP.io if you need a static IP

#35 by
Stephan

There are a number of free DynDNS services out there. Some with pretty nice web interfaces. Didn't know staticip.io, though. If you just want to get a domain name for your dynamic IP I would recommend using your favorite one. Much less hassle.

MiniDynDNS is for people who don't want to use such a service or want to roll their own (on a small scale). Or just want to play around with DNS I guess. :)

#36 by
Maxi

Hey i have a problem, i can't get minidyndns to work. The server works in my local network, i can do a nslookup foo.dyn.example.com 192.168.2.122 and it returns what i want, but i can't access it trough dns with dyn.example from the web. I set the A and NS record correctly. If i go to ns.example.com from my local network i end up at my routers website and from outside it gives me an error. What ip do i have to specifie in the config.yml, my internal or external?

#37 by
Stephan

Hi Maxi,

From what I understand there are two problem sources here:

a) Locally MiniDynDNS is working and you're resolving foo.dyn.example.com to 192.168.2.122. I assume "example.com" is just a placeholder for your own domain. This works in your network since IPs like 192.168.x.x are designed for local networks that don't need to be reachable via the internet. Every local network can use them work whatever they like and thus 192.168.2.122 can mean different devices depending on what local network you're in. Because of that those IP addresses don't work in the internet. Which of the many millions 192.168.2.122 would you like to talk to?

The MiniDynDNS answer has to contain the IP you can be reached with over the internet. For IPv4 this is the public IP of your router. You also have to add a port forwarding on your router so it knows which local IP you wan't to talk to depending on the port number used. With IPv6 this is a whole lot simpler but thanks to stupid software and routers comes with a lot of different problems.

b) You set ns.example.com to the IP of your router. But isn't that the IP that keeps changing? MiniDynDNS needs to run on a static IP so you can set the NS record to this static IP. Otherwise no one on the world would know where to find the MiniDynDNS server. The subdomains managed within the MiniDynDNS server then can change. But MiniDynDNS itself needs a static IP. There is no way around that.

Hope that helps Stephan

#38 by
Rick

Hey Stephan.

You wrote a nice app here. Many thanks for sharing it. While I have been using Linux for years, my first foray into Ruby territory. So far so good - well sorta.

I have the MiniDynDNS server up and running but am getting errors with SSL. Perhaps I was too bold and tried to use my wildcard certificate, but…..

I have server.key (with the private key) and server.pem with both the certificate and the intermediate.certificate in it (its a AlphaSSL signed cert). But it doesn't seem to liked it and honestly I am not sure where to go from here. Below is the error I get (followed by the system timing out waiting).

I appreciate any thoughts you might have on this. I am simply browsing to https://myservername.com using Chrome.

Rick

2019-07-05 09:21:36 HTTPS: Failed to process request: wrong number of arguments (given 1, expected 0) /usr/local/rvm/rubies/ruby-2.6.3/lib/ruby/2.6.0/openssl/ssl.rb:238:in `peeraddr' dns.rb:518:in `block in handle_http_connection' dns.rb:506:in `catch' dns.rb:506:in `handle_http_connection' dns.rb:703:in `block (3 levels) in <main>' dns.rb:700:in `each' dns.rb:700:in `block (2 levels) in <main>' dns.rb:696:in `loop' dns.rb:696:in `block in <main>'

P.S. Noticed your theme chnaged overnight. I liked the old one, but this is nice too.

#39 by
Stephan

Hi Rick,

Sorry that the reply took a while. I just came back from vacation. The bug is fixed in the new v1.3.1. It's enough to just download and replace dns.rb (https://github.com/arkanis/minidyndns/blob/master/dns.rb).

Turned out the certificates were all right. The problem was caused by looking up the IP address of the connection if none was specified in the URL. This worked for HTTP but not for HTTPS. OpenSSL asks the TCP socket to do this job but doesn't pass along any parameters to peeraddr(). That is where the error came from. I found a way around that and it's now covered by a test case. Thanks for the log snippet you posted. It really helped a lot! :)

Happy programming Stephan

P.S. The theme changes every day depending on the time. Just some quirky habit I caught for my personal website project. :D You can change it with the style switcher at the top of the page if the afternoon theme gets to exhausting to read (I'm not quite happy with the usability of it…).

#40 by
DaNiel L

Hi, Stephan. I Have a domain "universidad.edu.ec" problem when I try to delegate. dyn.universidad.edu.ec (10.253.254.9) to my miniDyndns My SOA is for example universidad.edu.ec with NS dnslocal1.universidad.edu.ec and this is the conf for bind slave zone zone "dyn.universidad.edu.ec" { type slave; masters { 10.253.254.9; }; file "/var/named/internal/dyn.uleam.edu.ec.slave"; }; If I use the miniDynDNS directly i can resolve without problems, but when i try to sync with SOA the slave show this error: 18-Mar-2020 18:46:48.430 xfer-in: error: transfer of 'dyn.universidad.edu.ec/IN/internal' from 10.253.254.9#53: failed to connect: connection refused MY SOA is slave from another Bind NS and dont have the same problems in my config.yml I Have this lines

domain: dyn.uleam.edu.ec soa: nameserver: dnslocal1.universidad.edu.ec mail: hostmaster@universidad.edu.ec

Any Solution ?

Sorry my bad English :) Best Regards,

#41 by
Stephan

Hi DaNiel L,

thanks for giving it a try. I'm not sure what the Bind config does but MiniDynDNS doesn't implement any kind of transfer protocol or record replication… just in case that's what you're trying to do.

From what I understand you're running MiniDynDNS on a server with the IP 10.253.254.9 at port 53. The rest isn't quite clear to me so I'll try to write what config should work:

a) The domain dnslocal1.universidad.edu.ec has to point to the server running MiniDynDNS. Meaning in the universidad.edu.ec zone there should be a record like this:

dnslocal1    A    10.253.254.9

That's the record outsiders will use to actually find your MiniDynDNS.

b) The universidad.edu.ec zone needs to delegate the "dyn" subdomain to your MiniDynDNS server. That is done with an NS record in the universidad.edu.ec zone. Together with the above record the universidad.edu.ec zone should contain something like that:

dyn          NS   dnslocal1.universidad.edu.ec
dnslocal1    A    10.253.254.9

The first record tells outsiders that everything in dyn.universidad.edu.ec is managed by the nameserver dnslocal1.universidad.edu.ec. And through the second record they know that this server has the IP 10.253.254.9. Thanks to that they know where to send the DNS request packets to.

As far as I can tell that should be it. Note that you only have to add the above two records to the universidad.edu.ec zone. You don't need to create the dyn.universidad.edu.ec zone I think.

The records of your MiniDynDNS should be available as foo.dyn.universidad.edu.ec, bar.dyn.universidad.edu.ec and so on.

Give it a try and let me know if that hepled. :)

Happy programming Stephan

#42 by
Reza

Hi there. I'm trying to setup you miniddns on my pi server (ubuntu) and I have some issues that I can't solve and I hope that you can help me. when I use ruby dns.rb this error apear:

dns.rb:646:in `bind': Cannot assign requested address - bind(2) for "my public static ip" port 53 (Errno::EADDRNOTAVAIL)

this is my config.yml:

domain: dyn.mydomain.com

dns: port: 53 ip: my public static ip http: port: 80 ip: my public static ip https: port: 443 ip: my public static ip

and this is my db,mydomain.com.yml :

--- SERIAL: 2015110200 foo: pass: oAKrrpozHCDRLyPp97T7umf648aiYQpL A: 192.168.0.1 AAAA: ff80::1 user1: pass: UjQFD9Vm3nU6uzn7GPDYeHt9xxRURid6 A: my public ip

user2: UjQFD9Vm3nU6uzn7GPDYeHt9xxRURid6 A: my local ip

when I take nslookup for user1.dyn.mydomain.com it says ** server can't find user1.dyn.mydomain.com: SERVFAIL what should i do???

thank you ra3320w@gmail.com

#43 by
Stephan

Hi Reza,

you can only run MiniDynDNS on a server that actually has a public static IP address. It'll serve as an address book for the rest of the world to look up your own subdomains. So it has to be directly reachable by the rest of the world. You get that with a virtual server rented from a webhosting company or a server in a data center. When you rent one of those they provide you with a public static IP address for the server and then you can run MiniDynDNS on that server.

"my public static ip" isn't supposed to be a placeholder but you should actually insert the IP address of your server there. Same goes for "mydomain.com", it's just a placeholder for your own domain (you'll also need to rent from a company).

Also IPs like 192.168.x.y or 10.x.y.z (where x, y and z are just some numbers) won't work. Those are private IPs, every private network (household, company, etc.) can use them for their devices. Meaning an IP like 192.168.0.1 will probably point to a different device in every household or company. Making that IP useless in the global internet because it doesn't identify a single target server.

From what you posted it doesn't look like you have the necessary infrastructure for MiniDynDNS. Sorry. :(

Happy programming Stephan

#44 by
Reza

Oh hi. thanks for your answer.The 'mydomain.com' was a placeholder for not revealing my domain name so do "my public static ip":DI rented a domain and I have my own web server in my house with a static public ip.I have done everything that you said in your website and github, but the error that I wrote in your website before, appears.What did I do wrong and What should I do?

Happy programming to you too ;-) Reza

#45 by
Anonymus

Hi Reza,

Oh, that made my previous reply sound rather silly, sorry about that. :D

With a static public IP you should have everything you need. But just to make sure I understand the setup: Does this public static IP lead diretly to your webserver or does your router use that IP and there is a port forwarding to the webserver?

The "bind: Cannot assign requested address" error itself happens if you try to start a server on an IP address that isn't assigned to the current system. This error would make sense to me in case your router uses that IP and the webserver is accessable via port forwarding. If so you'll have to tell MiniDynDNS to listen on the local IP address of the Pi:

dns:
  port: 53
  ip: local-address-of-pi
http:
  port: 80
  ip: local-address-of-pi
https:
  port: 443
  ip: local-address-of-pi

And configure some more port forwardings for UDP port 53 (DNS packets), TCP port 80 and 443 (the HTTP and HTTPS interfaces). You might want to change the HTTP and HTTPS ports though to not conflict with your webserver.

If your webserver diretly uses your public static IP I'm kind of baffled about the error. The only thing I can think of is to check what IPs are assigned to that system. Usually you can check this with the command "ip address show" or "ip a" for short. Your public static IP should be in there somewhere. If not there might be a configuration error on the system… or a simple typo somehere. Sorry I don't have any great ideas in that case. :(

Good luck Stephan

#46 by
Sören

Stephan, thank you very much for your effort bringing this nice MiniDynDNS on its way. I'm using it with Ruby 3.0.4 and it works well.

The only thing I didn't understand so far is, how to use it with TLS encryption. As far as I understood, I can use a certificate issued by Let's Encrypt but I'm wondering how this will work but I didn't try it so far. I will get started with a certificate for 'ns.mydomain.com' and will referr to this certificate in config.yml. Maybe I can get it running for self-signed certificates as well.

When you have any hints about that, I appreciate your comment.

Nevertheless, thank you very much for this tiny little helper!

#47 by
Stephan

Hi Sören,

Sorry it took me so long to reply. I've finally had some time and peace of mind to clean up all pending stuff of the project.

Using TLS is a pain and I've never tried it in minidyndns with anything other than self-signed certificates. In theory you can use Let's Encrypt certificates as long as the HTTPS server actually runs on the domain name specified in the certificate. But I doubt that it'll work with the certbot or any of the Let's Encrypt tools. minidyndns doesn't server static files so the certbot can't verify that you're the proper host.

Using a reverse proxy for HTTPS might be an easier way in the end. Especially if you already have an Apache or Nginx running on the server. Then this server can do the HTTPS part and just forward requests to minidyndns via plain old simple HTTP. I've just released v1.4.0 which also works behind a reverse proxy. So that might be worth a try.

Happy programming Stephan

#48 by
Sören

Hi Stephan

I did not try to use TLS so far but I configured fail2ban for your mini DynDNS.

To do that, I run the ruby script as a service. With systemd on AlmaLinux 9 (or similar), create a file for the service:

# /etc/systemd/system/dyndns.service

[Unit]
Description=Service for Mini DynDNS

[Service]
User=root
# change 'var/dyndns' to the path where the dns.rb and related files live
WorkingDirectory=/var/dyndns
# change '/usr/bin/ruby' to path where the ruby interpreter is located
ExecStart=/usr/bin/ruby dns.rb
ExecReload=kill -USR1 $MAINPID
ExecStop=kill -INT $MAINPID

# change log files as desired (folder must exist)
StandardOutput=append:/var/log/dyndns/standard.log
StandardError=append:/var/log/dyndns/error.log

[Install]
WantedBy=multi-user.target

Enable, start and check new service:

systemctl enable dyndns
systemctl start dyndns
systemctl status dyndns

I assume that fail2ban is already running. Create filter:

# /etc/fail2ban/filter.d/dyndns.conf

[Definition]
# don't forget to edit the ruby script to log host ip as well!
failregex = <HOST>.*not authorized

Create a jail for dyndns:

# /etc/fail2ban/jail.d/dyndns.local

[dyndns] # this must match with the name of the service
enabled = true
banaction = iptables # ban is applied only for port defined below

filter = dyndns
# make sure the log path matches the set path in dyndns.service file
logpath = /var/log/dyndns/standard.log

# adjust port, in my case dyndns is running at port 8080
port = 8080

# remove following two lines to apply default settings
bantime = -1
maxretry = 1

Adjust the jail as desired. In above example, having one valid request with wrong or missing authentication leads to a permanant ban of the host which requested your dyn dns. In my case there is no reason to be less restrictive because any user knows how to authenticate. And in other cases (mostly typos), I unblock them manually. This is maybe not applicable for (more) critical or bigger services. To extend the jail (e.g. to block hosts with bad request as well) you must adjust the filter (and the dns.rb to log host ip as shown next).

Last but not least, using the current version (1.4) of mini DynDNS requires to make little changes to get that jail working. Change at least the following line

# dns.rb (v1.4), line 617
log "#{log_prefix}: #{method} #{path_and_querystring} -> not authorized"

to

log "#{connection.peeraddr.last} #{log_prefix}: #{method} #{path_and_querystring} -> not authorized"

This is neccessary because fail2ban is reading the log files and is looking for all log entries matching the regular expression defined in the filter via failregex. When a match occurs, the given conditions will be checked (bantime, maxretry, findtime etc.) and actions (banaction, port etc.) are applied on matching host IP via iptables (or firewalld).

Don't forget to restart dyndns service to apply code changes with

systemctl restart dyndns

Because fail2ban is reading even older logs, it may occur that you ban yourself. You can undo this by

fail2ban-client set dyndns unbanip <ip to unban>

To check how much bans were applied and to prove the jail is working, run

fail2ban-client status dyndns

See you Sören

Leave a new comment

Having thoughts on your mind about this stuff here? Want to tell me and the rest of the world your opinion? Write and post it right here. Be sure to check out the format help (focus the large text field) and give the preview button a try.

optional

Format help

Please us the following stuff to spice up your comment.

An empty line starts a new paragraph. ---- print "---- lines start/end code" ---- * List items start with a * or -

Just to keep your skill sharp and my comments clean.

or