0xERR0R / blocky

Fast and lightweight DNS proxy as ad-blocker for local network with many features
https://0xERR0R.github.io/blocky/
Apache License 2.0
4.8k stars 210 forks source link

Blocky should have a `reload` option to re-read the configuration #607

Open davefullard opened 2 years ago

davefullard commented 2 years ago

When making changes to the Blocky configuration, it would be nice to have a way to re-read the configuration without having to stop and start the process, especially when using large blocklists like OISD take time to load on slower devices like a Raspberry Pi or PCEngines APU before DNS resolution becomes available. Maybe by simply sending a HUP signal or something?

jckefan commented 2 years ago

It's a good suggestion, though it should, not only check for config file update, but also for updates made to the TLS certificate the user is supplying to Blocky. As it happens most free TLS certificate providers (Let's Encrypt, ZeroSSL, etc.) only provide certificates which are valid up to 90 days. And every time ACME client renews the certificate (which usually happens after 60 days). You've to restart Blocky so it can import the newer TLS certificates (into its memory?)

Rereading of config file and TLS certs. on-the-fly can provide great benefits to the everyday users as well as those who are running their Blocky setup in the cloud.

0xERR0R commented 2 years ago

related to #462

shizunge commented 9 months ago

If you only need to reload blocking/allowing lists, see this discussion https://github.com/0xERR0R/blocky/discussions/1347

I have published my solution to update the lists without restarting blocky.

ThinkChaos commented 7 months ago

Some thoughts prompted by discussion in #1448.
I'm not hard set on any of this, just thoughts for a possible implementation. And also not actively working on it.

I think the best thing would be to basically rebuild all the resolvers so the new config propagates without us having to add some error prone patching logic (since a lot of data is derived from config, having more than one code path doing that seems risky).
For optimization, we can rely on stuff like list caching which we want anyways.

The "hot reload" part would be to gracefully stop the old server, and make the new one re-use the network sockets.
Based on http.Server.Shutdown it seems standard Go servers aren't designed for that: Shutdown closes the sockets it uses, so we can't use it, otherwise there's a period of time where clients might see "connection refused".

So using that API as is, we're forced to keep the old server around until Accept returns naturally, which only happens at the next connection/request. At that point the server can check a flag to see it's been asked to stop.
I think we really shouldn't drop that request, so we can either handle it with the old config just using the normal flow (not ideal since some sockets might be rarely used, in which case you'd still end up using the old config long after the reload); or figure out a way to pass that request to the new server (a chan maybe).
That request could maybe be done at a lower level than the server: net.Listener is an interface, so we could have our own implementation that wraps an existing one, and provides a StealSocket method that both returns a new instance of our struct we transfer ownership of the socket to, and causes any in progress Accept on the original Listener to return an error. That way the old server can stop immediately and we have a new Listener for the new server to use. I'm not really sure if that's actually doable, but from a quick look at the net APIs I think it's worth a shot. It would also need to work for net.PacketConn for datagram based transports.

Using SO_REUSEPORT is another possibility since with that you can actually close a socket after having opened another so you're sure no clients will be refused. That means there's a window where you have both sockets open, so both configs active at the same time. Though compared to using a single socket you can close the listener, so that window is guaranteed to be short. I'd prefer the first approach because SO_REUSEPORT also allows other programs to create sockets on the same addr+port.

This is all a bunch of complexity so IMO an inital implementation where we do drop requests is fine, anyways most DNS clients have retries built in.