Bug: Noip IPv6 update also updates IPv4 #366

Closed
opened 2025-11-20 04:23:11 -05:00 by saavagebueno · 11 comments
Owner

Originally created by @incaseoftrouble on GitHub (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.

Originally created by @incaseoftrouble on GitHub (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.
Author
Owner

@qdm12 commented on GitHub (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.

@qdm12 commented on GitHub (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.
Author
Owner

@incaseoftrouble commented on GitHub (Feb 8, 2024):

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"
    }
  ]
}
@incaseoftrouble commented on GitHub (Feb 8, 2024): Oops, forgot to include my config. No, it is false! ```json { "settings": [ { "provider": "noip", "domain": "ddns.net", "host": "<hostname>", "username": "<user>", "password": "<pass>", "ip_version": "ipv6", "provider_ip": false, "ipv6_suffix": "::x:y:z/64" } ] } ```
Author
Owner

@qdm12 commented on GitHub (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.

@qdm12 commented on GitHub (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`.
Author
Owner

@incaseoftrouble commented on GitHub (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 quentin.mcgaw@gmail.com; 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 commented on GitHub (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 quentin.mcgaw@gmail.com; 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?)
Author
Owner

@incaseoftrouble commented on GitHub (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.

@incaseoftrouble commented on GitHub (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.
Author
Owner

@qdm12 commented on GitHub (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 cb3075ea32 (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 commented on GitHub (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 cb3075ea325d10522c2e8d9b0b332e88dcd7e705 (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?
Author
Owner

@incaseoftrouble commented on GitHub (Feb 9, 2024):

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 cb3075ea32 (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 :)

@incaseoftrouble commented on GitHub (Feb 9, 2024): > 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 https://github.com/qdm12/ddns-updater/commit/cb3075ea325d10522c2e8d9b0b332e88dcd7e705 (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 :)
Author
Owner

@qdm12 commented on GitHub (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 cb3075ea32/pkg/publicip/http/providers.go (L68-L99) 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 https://github.com/qdm12/ddns-updater/issues/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 😄

@qdm12 commented on GitHub (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 https://github.com/qdm12/ddns-updater/blob/cb3075ea325d10522c2e8d9b0b332e88dcd7e705/pkg/publicip/http/providers.go#L68-L99 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 https://github.com/qdm12/ddns-updater/issues/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** 😄
Author
Owner

@incaseoftrouble commented on GitHub (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 commented on GitHub (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](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html) 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).
Author
Owner

@incaseoftrouble commented on GitHub (Feb 9, 2024):

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?

@incaseoftrouble commented on GitHub (Feb 9, 2024): 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?
Author
Owner

@qdm12 commented on GitHub (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 😄

@qdm12 commented on GitHub (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 in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/ddns-updater#366