cloudflare-ddns icon indicating copy to clipboard operation
cloudflare-ddns copied to clipboard

Specify the ethernet interface

Open favonia opened this issue 1 year ago • 1 comments

Inspired by https://github.com/TimothyYe/godns/issues/238: maybe there are still cases where manual specification of the interface makes sense.

favonia avatar Mar 14 '24 13:03 favonia

The Go standard library does not provide an easy way to specify the interface for the current detection algorithm. :upside_down_face:

favonia avatar Jul 19 '24 13:07 favonia

@favonia Hello. I am interested in specifying a custom interface.

I have little experience with go, but shouldn't something like this https://gist.github.com/schwarzeni/f25031a3123f895ff3785970921e962c work? I tried it and it looks like it works (I have not tried it on Windows).

What I ended up doing is run cloudflare-ddns in a bridged network and create a small function to get the interface on the host machine. I spinned up a server listening on a docker IP like 172.xx.0.1:9000 and then specify the provider as url like url:http://172.xx.0.1:9000/ip

I came across a python version here https://stackoverflow.com/a/24196955. One can use flask to serve. There is also a node version here https://stackoverflow.com/a/8440736 which can be served with something like express.

It is a bit dirty but this should work even with custom commands to get an IP that is not directly related to an interface.

tchar avatar Sep 03 '24 18:09 tchar

What I ended up doing is run cloudflare-ddns in a bridged network and create a small function to get the interface on the host machine. I spinned up a server listening on a docker IP like 172.xx.0.1:9000 and then specify the provider as url like url:http://172.xx.0.1:9000/ip

This is way too complicated... you deserve a much better solution!!!

The code you showed will choose the first IP of the specified interface in the expected IP family, while the current local provider will use one of the IPs (if more than one) for connecting to the internet. Ideally, the implementation will inspect the routing table etc. to find out the path to the internet via a specific interface. Ideally, there should be a way to hack the standard library to force the interface while trying to connect to the internet. It's just that I don't know how to do it yet. :upside_down_face:

favonia avatar Sep 03 '24 23:09 favonia

@tchar Hi, I think more about this and feel the design is a bit unclear to me. So I'd like to just ask you directly: if you specify an interface and it magically has many seemingly valid IP addresses, what do you want the updater to do?

favonia avatar Sep 13 '24 13:09 favonia

Hey @favonia indeed it seems like you would have to compromise, although I'm curious how often does something like this is the case.

One possible solution is to allow the user to select family (ipv4 vs ipv6) and index (0, 1, ...).

Now the family is clear, but the question is: Is it possible for the code to run two consecutive times and give a different order for the IPs?

If the answer is no, or unlikely, you could do it like in the following scenario.

Say machine has three IPs on interface eth1: 10.0.0.2 and 10.0.0.3 and some ipv6.

Specifying something like

local:ipv4:eth1/0 would give 10.0.0.2, and local:ipv4:eth1/1 would give 10.0.0.3 local:ipv4:eth1/2 would give empty local:eth1/2 would give the ipv6

tchar avatar Sep 13 '24 15:09 tchar

@tchar The ipv4/6 would not be needed because provider is set separately for IP4/6_PROVIDER :wink:. BTW, by any change, are you using url:http://172.xx.0.1:9000/ip to transfer an IPv6 address? If so, it might stop working in the upcoming version...

favonia avatar Sep 13 '24 16:09 favonia

@tchar The ipv4/6 would not be needed because provider is set separately for IP4/6_PROVIDER 😉. BTW, by any change, are you using url:http://172.xx.0.1:9000/ip to transfer an IPv6 address? If so, it might stop working in the upcoming version...

@favonia Yes, I am using this, but for ipv4, not ipv6.

If you would put the correct number to xx I would be concerned haha. You nailed the port and almost the path.

tchar avatar Sep 13 '24 16:09 tchar

Well I'm only using the public information you provided in the issue. @tchar

favonia avatar Sep 13 '24 16:09 favonia

@tchar Alright, I did some more homework, and have some good and bad news.

  • The bad news is that not being able to specify the interface directly is a known weakness of the POSIX socket API. The Go standard library cannot do much here. There's no way to say "use eth0 to connect to 1.1.1.1". (The only exception is IPv6 link-local addresses, but let's ignore them for now.)
  • The semi-good news is that even the Linux kernel seems to be choosing the "first" IP address that "makes senses" if existing hints did not sufficiently ping down the address. So, "choosing the first one" is expected. There are many criteria we could use to determine whether an address is "reasonable":
    • Obviously, for IPv4 you ignore IPv6 addresses and vice versa. (However, should IPv4-mapped IPv6 addresses treated as IPv4 ones or just rejected?)
    • Next, we probably want to skip IPv6 link-local addresses because an IPv6 interface would likely have one.
    • Maybe we should also ignore multicast IP addresses.
    • IPv4-mapped IPv6 addresses would be suspicious...

May I have your confirmation that your address will be some global unicast (public) IPv4 address? (That is, would the most conservative implementation that rejects all "weird" addresses work for you?) Also, may I learn more about your network setup, especially why the current automatic selection fails?

If you are not sure whether a particular address is global unicast, you can change the ip = ... line in the following GO program and run it: https://go.dev/play/p/fR2wL6aKDAI

favonia avatar Sep 21 '24 21:09 favonia

@favonia I am not sure what is considered to be a weird address. Unless you mean local and reserved addresses.

In my case I am using a VPN tunnel for VLAN.

The IPv4 is a valid one, but any service can be accessed only from those in the VPN VLAN network.

I hope this makes more sense.

tchar avatar Sep 21 '24 21:09 tchar

@tchar Thanks. I have three questions:

  1. Are your private IPs within the standard ranges? If so, they are okay.
    • 10.0.0.0/8
    • 172.16.0.0/12
    • 192.168.0.0/16
  2. I suppose using the VPN is the reason why connecting to some public website (e.g., 1.1.1.1) does not work, right? That is, the current detection code for "local" would give you a non-VPN IP.
  3. If this is a VPN totally under your control, perhaps you could assign the same IP to your machine every time? (That is, maybe you do not need DDNS at all.)

favonia avatar Sep 21 '24 21:09 favonia

@favonia

Here is an example of imaginary interfaces to help you understand a bit how the system works. eth0: Normal interface tun0: Interface for VPN tun1: Interface for VPN VLAN docker0-xx: Docker interfaces

  1. The IPs I see do not belong in these ranges. I am not sure if they will ever be in these ranges, but I think not since the machine has to be discoverable outside of its private network.
  2. The thing is that the machine has a ton of interfaces. Some belong to docker, some belong to VPN tunnels (not the VPN VLAN), and there is one for the VPN VLAN. I am not sure what the local algorithm does, but since I cannot pick an interface, I am using the aforementioned solution. Keep in mind that when the machine wants to connect to the internet, eth0 or tun0 may be used, but not tun1, unless the traffic is intended for one of the other machines in the VPN VLAN.
  3. I cannot assign the same IP, and I am not sure it is the best practice to do so. If I could/would, I guess I would not use DDNS at all.

Let me know if you need any other information.

tchar avatar Sep 21 '24 21:09 tchar

@tchar Thank you! Is the (first) IP assigned to tun1 the IP you want to use to update DNS records? (I am just checking that the algorithm in my mind would work.)

favonia avatar Sep 21 '24 21:09 favonia

Yes, there is only one IPv4 under that interface.

tchar avatar Sep 21 '24 21:09 tchar

@tchar BTW, the Go code you mentioned earlier (which is based on the code on stackoverflow) is not correct. It's assuming that the Go standard library always returns something of type *net.IPNet for interface addresses. I just checked the source code and it could be *net.IPAddr on Darwin, FreeBSD, and Windows. So, yeah, if you try it on Windows I guess it would fail.

favonia avatar Sep 22 '24 05:09 favonia

@tchar You may try out ~~the specify-iface branch (PR #941)~~ the edge Docker tag with the IP provider local:tun1. I still need to fine-tune the messages and testing. I have two questions for you:

  1. Is the syntax local:tun1 "obvious, stupid, crystal clear without reading the manual" to you?
  2. If you have tried out the feature, does it work for you?

favonia avatar Sep 23 '24 16:09 favonia

@favonia I tested it out, and it works. I only had to put it in host mode instead of bridge.

Regarding the syntax, it makes sense, so there is no problem. If I were you, I would prefix the interface with something like if. For example. local:if/tun1 since / is an invalid name for interfaces.

You could even have a simple mode (local:tun1) and a verbose mode (local:if/tun1) in case you want to reserve local for something more than interfaces in the future.

Alternatively, you could use a keyword that is different from local (like interface).

This is up to you; either works and makes sense.

tchar avatar Sep 26 '24 18:09 tchar

@tchar Wow, that's a great suggestion! I propose using

IP6_PROVIDER=local.iface:eth0

This leverages the existing cloudflare.doh and cloudflare.trace providers, though I admit the syntax might be a bit confusing: it reads more like local.(iface:eth0) instead of the intended (local.iface):eth0. I considered alternatives like local.if:eth0 and local.interface:eth0, but if might be misunderstood as a condition, and ironically, interface feels less clear than the abbreviation iface to me.

Once we establish local.iface, we could potentially introduce local.route:host and rebrand local as

IP6_PROVIDER=local.route:api.cloudflare.com

I wonder what you think about these changes? I am making the changes now, but it is easy to adjust the syntax before the release.

favonia avatar Sep 27 '24 02:09 favonia

@tchar I released 1.15.0 with the syntax local.iface:<iface>, but clearly documented that the syntax will not be finalized until 1.16.0. We have lots of time to pin down the design. :grin:

favonia avatar Oct 01 '24 13:10 favonia

@favonia That's great. I will pin the version to 1.15.0 so I don't get any surprises for now.

Thanks

tchar avatar Oct 03 '24 16:10 tchar

@tchar Thanks. It sounds like you don't have any preference between local.iface:tun2 and local:iface/tun2? I'm very happy with the actual code, so the format is the only remaining issue :wink:

favonia avatar Oct 03 '24 16:10 favonia

@favonia As a user, I find these formats identical in usability. As a dev, it's your project; you should decide which design is best for the project's life. You know it inside out.

Both formats are better than local: because they are more verbose. Other than that. I am happy.

If I were you, I would probably think about what other use cases the settings can have and see what the "key" would look like.

  1. <main>.<sub>:<value>
  2. <main>:<sub>/<value>

I feel like 1 (which is your current implementation feels better)

tchar avatar Oct 03 '24 17:10 tchar

Closing this as there's nothing to do for now.

favonia avatar Oct 04 '24 17:10 favonia