Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Noip IPv6 update also updates IPv4 #633

Closed
incaseoftrouble opened this issue Feb 5, 2024 · 11 comments
Closed

Bug: Noip IPv6 update also updates IPv4 #633

incaseoftrouble opened this issue Feb 5, 2024 · 11 comments

Comments

@incaseoftrouble
Copy link

incaseoftrouble commented Feb 5, 2024

TLDR: An update to noip with "ip_version": "ipv6" also modifies the IPv4

  1. Is this urgent: No
  2. DNS provider(s) you use: NoIP
  3. Program version: 2.6.0
  4. What are you using to run the container: docker-compose

In summary: I'm now using ddns-updater for my IPv6 address, however unfortunately the updates also modify the A record. I don't know if this happens from noip side or this client. If it is the former, it would be nice to be able to specify an optional, fixed IPv4 IP. If it is the latter, it would probably be nice to have ip_version: "ipv4" / ip_version: "ipv6" mean exclusively update these and add ip_version: "ipv4 and ipv6" the current behavior (or, to maintain compatibility, ip_version: "ipv6_only"?).

Why this is useful: I have a dual stack connection, meaning that I get a public IPv6 but not a proper public IPv4. I solved this by pointing IPv4 traffic to an AWS instance, which, of course, has a different IP, and then proxy the traffic to the appropriate service. Updating the noip address to my "fake" public IPv4 breaks this.

For me, the issue is not urgent since my IPv6 doesn't change too often + I currently rarely need IPv4 connection.

EDIT: I think the current behavior for ip_version: "ipv6" also isn't exactly "update both" since the current IPv4 is not checked for updating, only when an update is triggered because the IPv6 differs, then the IPv4 is also changed.

@qdm12
Copy link
Owner

qdm12 commented Feb 8, 2024

Are you using provider_ip as true? Because that one perhaps updates both ipv4 and ipv6 🤔 I'm thinking of removing it entirely for all providers too, so maybe something I can prioritize.

@incaseoftrouble
Copy link
Author

Oops, forgot to include my config. No, it is false!

{
  "settings": [
    {
      "provider": "noip",
      "domain": "ddns.net",
      "host": "<hostname>",
      "username": "<user>",
      "password": "<pass>",
      "ip_version": "ipv6",
      "provider_ip": false,
      "ipv6_suffix": "::x:y:z/64"
    }
  ]
}

@qdm12
Copy link
Owner

qdm12 commented Feb 8, 2024

What do you get in the logs when using LOG_LEVEL=debug? It should log the url queried etc we're looking for the ones with https://dynupdate.no-ip.com/nic/update.

@incaseoftrouble
Copy link
Author

incaseoftrouble commented Feb 8, 2024

Editing out some info:

2024-02-08T10:02:01+01:00 INFO Reading history from database: domain ddns.net host <host> ipv6
2024-02-08T10:02:01+01:00 INFO [backup] each 168h0m0s; writing zip files to directory /updater/data
2024-02-08T10:02:01+01:00 DEBUG configured to fetch IP: v4 or v6: false, v4: false, v6: true
2024-02-08T10:02:01+01:00 INFO [healthcheck server] listening on 127.0.0.1:9999
2024-02-08T10:02:01+01:00 INFO [http server] listening on 127.0.0.1:8000
2024-02-08T10:02:01+01:00 DEBUG your public IP address are: v4 or v6: invalid IP, v4: invalid IP, v6: <IPv6>
2024-02-08T10:02:01+01:00 INFO ipv6 address of megglab.ddns.net is <something> and your ipv6 address  is <IPv6>
2024-02-08T10:02:01+01:00 INFO Updating record [domain: ddns.net | host: <host> | provider: noip | ip: ipv6] to use <IPv6>
2024-02-08T10:02:01+01:00 DEBUG GET https://<auth>@dynupdate.no-ip.com/nic/update?hostname=<host>&myipv6=<URLencoded IPv6> | headers: User-Agent: DDNS-Updater [email protected]; Authorization: Basic <auth>
2024-02-08T10:02:02+01:00 DEBUG 200 OK | headers: Server: nginx; Content-Type: text/plain; charset=UTF-8; Connection: keep-alive; Cache-Control: no-cache, private; Date: Thu, 08 Feb 2024 09:02:02 GMT | body: good <IPv4>,<IPv6>

It seems that no-ip tries to be clever and just updates the IPv4 if you don't provide one. In particular the IPv4 I get in the response body is my current IPv4, not the one I configured.

(TBH I have no idea how they even get the IPv4? Maybe the ddns updater is connecting to dynupdate.no-ip.com via IPv4?)

@incaseoftrouble
Copy link
Author

incaseoftrouble commented Feb 8, 2024

Aha. dynupdate.no-ip.com does not have an AAAA record. I think there should be something like ip1.dynupdate6.no-ip.com to connect via ipv6. Probably one should use that one if ip_version is ipv6? That would exclude the case where you are in an IPv4 only network but want to update an IPv6 address. Which might actually happen if the container is running within an IPv4 docker network, but then you can't even get IPv6 addresses I guess.

@qdm12
Copy link
Owner

qdm12 commented Feb 9, 2024

On the Noip webpage they only mention dynupdate.no-ip.com in https://www.noip.com/support/knowledgebase/how-to-configure-ddns-in-router
Apparently, noip uses the same dyndns2 protocol as described in https://help.dyn.com/remote-access-api/perform-update/
That page precises the myip parameter works for both ivp4 and ipv6, however ddns-updater's code for now uses the query parameter myip for IPv4 addresses, and myipv6 for iPv6 addresses, not too sure why. I think that's likely the problem: noip is not aware of the myipv6 parameter and so it thinks no specific ip address was sent (since myip is not there), and thus uses your IPv4 address detected. This is changed to use myip= for all IP versions in commit cb3075e (latest image).

Actually, one more thing, I think that the provider ip parameter is a rather big problem if your public ip address is an IPv6 address (detected if version is ipv4 or ipv6 or ipv6), because it will likely set an ipv4 address detected by their server, and not an IPv6 address, despite DDNS updater wanting to update with an IPv6 address 🤔 Thoughts?

@qdm12 qdm12 closed this as completed Feb 9, 2024
@incaseoftrouble
Copy link
Author

I think that's likely the problem: noip is not aware of the myipv6 parameter and so it thinks no specific ip address was sent (since myip is not there), and thus uses your IPv4 address detected. This is changed to use myip= for all IP versions in commit cb3075e (latest image).

I think they are aware, in the sense that the IPv6 updates do work, and they do not infer the IPv6 from the connection (for two reasons: a) they can't, because the connection is IPv4 and b) updating records to an IPv6 that is not the one of the device does also work, using ipv6_prefix). I think given that the myip is missing they automatically infer the IPv4 from the connection. (Which is maybe what you meant? I.e. the part of their code that looks at myip does not see that myipv6 is there and then infers the address from the connection, so that part thinks it has to infer the IP)

I agree with always using myip if that works for IPv6, too. (I will try out the image later)

Actually, one more thing, I think that the provider ip parameter is a rather big problem if your public ip address is an IPv6 address (detected if version is ipv4 or ipv6 or ipv6), because it will likely set an ipv4 address detected by their server, and not an IPv6 address, despite DDNS updater wanting to update with an IPv6 address 🤔 Thoughts?

Yes and no. It depends how you connect to the provider. If you can connect to them with ipv6, they could infer your ipv6 and vice versa. But you could connect with ipv4 to update an ipv6 address etc. I think there is a way to make it work, but that just adds so much (probably unnecessary) complexity.

I think that it is fair to require that any update entry in config.json is EITHER v4 or v6, since their behavior and mechanisms are so distinct. That would also make your life easier and the behavior clearer, automatically inferring whether the updater should use ipv4 or ipv6 could lead to weird things if, e.g., the updater is on a machine with multiple network connections.

I don't actually know how fixed the priorities are? Or is IPv6 always preferred? I guess the logic could be to first try to bind a IPv6 connection and, if that doesn't work, pick an IPv4. But what if that changes? Is it an error?

I think I would be happy if ip_version isn't set, the updater once infers whether IPv4 or IPv6 is appropriate and then writes that to the configuration, reporting an error if later on ipv6 isn't possible.

Sorry for the somewhat unsorted thoughts, just putting out there what I am thinking :)

@qdm12
Copy link
Owner

qdm12 commented Feb 9, 2024

I think that it is fair to require that any update entry in config.json is EITHER v4 or v6, since their behavior and mechanisms are so distinct. That would also make your life easier and the behavior clearer, automatically inferring whether the updater should use ipv4 or ipv6 could lead to weird things if, e.g., the updater is on a machine with multiple network connections.

Indeed, the reason for the ipv4 or ipv6 is due to the public ip services not specific to an ip version. See

switch version {
case ipversion.IP4:
switch provider { //nolint:exhaustive
case Ipify:
url = "https://api.ipify.org"
case Ipleak:
url = "https://ipv4.ipleak.net/json"
}
case ipversion.IP6:
switch provider { //nolint:exhaustive
case Ipify:
url = "https://api6.ipify.org"
case Ipleak:
url = "https://ipv6.ipleak.net/json"
}
case ipversion.IP4or6:
switch provider {
case Ipify:
url = "https://api64.ipify.org"
case Google:
url = "https://domains.google.com/checkip"
case Ifconfig:
url = "https://ifconfig.io/ip"
case Ipinfo:
url = "https://ipinfo.io/ip"
case Spdyn:
url = "https://checkip.spdyn.de"
case Ipleak:
url = "https://ipleak.net/json"
}
For example https://api.ipify.org is for IPv4 only, https://api6.ipify.org is for IPv6 only and https://api64.ipify.org is for one or the other. There are a few others not specific to an IP version, for example https://domains.google.com/checkip. Maybe it would be worth it to remove them and remove the ipv4 or ipv6 option, anyway it seems rather unused by users since it's rather confusing. But that would break some compatibility here and there, I created #642 to track changes to be made for a v3 version.

Or is IPv6 always preferred?

Not really, it depends on the ip echo service, which is a bit... unknown I guess. For dual stack, one should really not use ipv4 or ipv6. If ip_version isn't set it defaults to ipv4 or ipv6 and let the ip echo service send whatever IP version they prefer.

Feel free to shoot more unsorted thoughts 😄

@incaseoftrouble
Copy link
Author

incaseoftrouble commented Feb 9, 2024

Not really, it depends on the ip echo service, which is a bit... unknown I guess.

As I understand it, that in turn simply depends on what address type the client connection uses. I'm not an expert in networking etc. but AFAIK one can (optionally) fix which address type should be used when creating a socket / connection, and otherwise the system chooses.

So if you (just) write curl https://api64.ipify.org then (I think) the OS determines (among others) which IP type to use (I think this is where the magic happens on Linux). I'm guessing that most systems try IPv6 first -- after all, its supposed to replace IPv4 ... eventually :D --, at least if the host name has both AAAA and A records. Then, the echo service simply sees an incoming IPv6 connection and responds with the source address.

What I want to say is that the echo service really has no choice, they only see either or. Instead, if the ddns-updater does not specify which address type to use, then the OS determines what will happen. Here, ip_version could be used to fix the address type (in GAI-terms, specify AF_INET or AF_INET6, I don't know how to do that in Go but according to StackOverflow one should be able to specify "tcp6" or "tcp4" somewhere).

@incaseoftrouble
Copy link
Author

Just to confirm: The change seems to work. The response body of noip now also only indicates the new ipv6.

Also, for reference, this discussion seems to be related to #507.

What could be possible to merge these ideas to not have an ip_version field but maybe two fields "ipv6": { prefix, ... } and "ipv4": { ... } or similar, and only update those address families that have been specified.

In case both are specified, there definitely need to be two separate calls to determine the public addresses, and then potentially one or two updates to the provider, depending on what is supported.

I guess a good idea would be something like 1) check which address families are required by any configuration entry 2) query those 3) group "necessary" updates by provider and then let provider-specific logic run all the updates?

@qdm12
Copy link
Owner

qdm12 commented Feb 9, 2024

What could be possible to merge these ideas to not have an

I would rather keep it to ip_version and have the options ipv4 and ipv6 and that's it. The program can then merge configs together for ipv4+ipv6. But for a v3 breaking change release, perhaps 🤔

In case both are specified, there definitely need to be two separate calls to determine the public addresses

  1. check which address families are required by any configuration entry 2) query those

That's already done 😉 (once for ipv4 if any record is to be updated for ipv4, and once for ipv6 if any record is to be updated for ipv6). We just need to do #644 and also to, ideally, remove ipv4 or ipv6.

and then potentially one or two updates to the provider, depending on what is supported.
3) group "necessary" updates by provider and then let provider-specific logic run all the updates?

Indeed, some providers require (desec.io) to update both v4 and v6 at the same time, this should be done soon-ish with issue #507 but most providers only support a single ip, so it's a bit of a tough design to figure out 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants