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

T4930: Allow WireGuard peers via DNS hostname #4200

Open
wants to merge 9 commits into
base: current
Choose a base branch
from

Conversation

sskaje
Copy link
Contributor

@sskaje sskaje commented Nov 20, 2024

Change Summary

T4930: Allow WireGuard peers via DNS hostname + new script resetting peer
T4930: Ensure peer is created even if dns not working
T4930: limit wg retry times by using environment variable
T4930: make wg dns retry configurable through interfaces wireguard wgX max-dns-retry

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes)
  • Migration from an old Vyatta component to vyos-1x, please link to related PR inside obsoleted component
  • Other (please describe):

Related Task(s)

https://vyos.dev/T4930

Related PR(s)

Component(s) name

wireguard

Proposed changes

  1. Support wireguard endpoint using domain as peer address, make sure wg retries for dns resolution no more than 5 times;
  2. Introduce an op mode command reset wireguard, if user want to use wg set to force wireguard redo dns resolution;
  3. Detail changes like splitting Wireguard peer creation into peer creation + endpoint setup, with exception (need help on correct exception/log handling, not found in docs.vyos.io)

How to test

config mode

vyos@vyos# set interfaces wireguard wg0 peer wg0-xxx address t2.vm.xxx.xxx
[edit]
vyos@vyos# commit
[edit]
vyos@vyos#

op mode

# reset all peers on wg0
vyos@vyos:~$ reset wireguard interface wg0
Resetting wg0 peer YYYY= endpoint to t1.vm.xxx.xxx:ppppp ... done
Resetting wg0 peer XXXX= endpoint to t2.vm.xxxx.xxx:ppppp ... done

# reset single peer on wg0
vyos@vyos:~$ reset wireguard interface wg0  peer wg0-xxx
Resetting wg0 peer XXXX= endpoint to t2.vm.xxxx.xxx:ppppp ... done

vyos without working dns

I've provided screenshots in task's comments.

configure max dns resolution retry times

vyos@vyos# set interfaces wireguard wg0
Possible completions:
+  address              IP address
   description          Description
   disable              Administratively disable interface
   domain-name          Endpoint Domain Name
   fwmark               A 32-bit fwmark value set on all outgoing packets (default: 0)
 > ip                   IPv4 routing parameters
 > ipv6                 IPv6 routing parameters
   max-dns-retry        Max retry when DNS resolves failed. (default: 3)
 > mirror               Mirror ingress/egress packets
   mtu                  Maximum Transmission Unit (MTU) (default: 1420)
+> peer                 peer alias
   per-client-thread    Process traffic from each client in a dedicated thread
   port                 Port number used by connection
   private-key          Base64 encoded private key
   redirect             Redirect incoming packet to destination
   vrf                  VRF instance name


[edit]
vyos@vyos# set interfaces wireguard wg0 max-dns-retry
Possible completions:
   <1-15>               Max retry times



[edit]
vyos@vyos# set interfaces wireguard wg0 max-dns-retry 3
[edit]
vyos@vyos#

Smoketest result

root@vyos:/home/vyos# python3 /usr/libexec/vyos/tests/smoke/cli/test_interfaces_wireguard.py
test_01_wireguard_peer (__main__.WireGuardInterfaceTest.test_01_wireguard_peer) ... ok
test_02_wireguard_add_remove_peer (__main__.WireGuardInterfaceTest.test_02_wireguard_add_remove_peer) ... ok
test_03_wireguard_same_public_key (__main__.WireGuardInterfaceTest.test_03_wireguard_same_public_key) ... ok
test_04_wireguard_threaded (__main__.WireGuardInterfaceTest.test_04_wireguard_threaded) ... ok
test_05_wireguard_peer_pubkey_change (__main__.WireGuardInterfaceTest.test_05_wireguard_peer_pubkey_change) ... ok

----------------------------------------------------------------------
Ran 5 tests in 52.648s

OK
root@vyos:/home/vyos#

Checklist:

  • I have read the CONTRIBUTING document
  • I have linked this PR to one or more Phabricator Task(s)
  • I have run the components SMOKETESTS if applicable
  • My commit headlines contain a valid Task id
  • My change requires a change to the documentation
  • I have updated the documentation accordingly

Copy link

github-actions bot commented Nov 20, 2024

👍
No issues in PR Title / Commit Title

@sever-sever
Copy link
Member

sever-sever commented Nov 20, 2024

Build package fails (based on CI)

PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose
*** Error compiling './python/vyos/ifconfig/wireguard.py'...
  File "./python/vyos/ifconfig/wireguard.py", line 179
    f'Resetting {self.config['ifname']} peer {public_key} endpoint to {address}:{port} ... ',
                              ^^^^^^
SyntaxError: f-string: unmatched '['

make[2]: *** [Makefile:89: test] Error 1
make[2]: Leaving directory '/__w/vyos-1x/vyos-1x/packages/vyos-1x'
make[1]: *** [debian/rules:31: override_dh_auto_build] Error 2
make[1]: Leaving directory '/__w/vyos-1x/vyos-1x/packages/vyos-1x'
make: *** [debian/rules:21: build] Error 2
dpkg-buildpackage: error: debian/rules build subprocess returned exit status 2
Error: Process completed with exit code

@sskaje
Copy link
Contributor Author

sskaje commented Nov 20, 2024

Build package fails (based on CI)

PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose
*** Error compiling './python/vyos/ifconfig/wireguard.py'...
  File "./python/vyos/ifconfig/wireguard.py", line 179
    f'Resetting {self.config['ifname']} peer {public_key} endpoint to {address}:{port} ... ',
                              ^^^^^^
SyntaxError: f-string: unmatched '['

make[2]: *** [Makefile:89: test] Error 1
make[2]: Leaving directory '/__w/vyos-1x/vyos-1x/packages/vyos-1x'
make[1]: *** [debian/rules:31: override_dh_auto_build] Error 2
make[1]: Leaving directory '/__w/vyos-1x/vyos-1x/packages/vyos-1x'
make: *** [debian/rules:21: build] Error 2
dpkg-buildpackage: error: debian/rules build subprocess returned exit status 2
Error: Process completed with exit code

Updated.

My IDE was set python3.12, that syntax is acceptable in 3.12 but not in 3.11 XD

Copy link
Member

@sarthurdev sarthurdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the WG_ENDPOINT_RESOLUTION_RETRIES var block execution while waiting to resolve?

op-mode-definitions/reset-wireguard.xml.in Outdated Show resolved Hide resolved
op-mode-definitions/reset-wireguard.xml.in Outdated Show resolved Hide resolved
@sskaje
Copy link
Contributor Author

sskaje commented Nov 20, 2024

Does the WG_ENDPOINT_RESOLUTION_RETRIES var block execution while waiting to resolve?

Sorry, I don't understand your question.

WG_ENDPOINT_RESOLUTION_RETRIES is an environment variable use in wg command, source here:

wg reads env:
https://git.zx2c4.com/wireguard-tools/tree/src/config.c#n180

wg assigns as local variable:
https://git.zx2c4.com/wireguard-tools/tree/src/config.c#n199

wg loops and retries:
https://git.zx2c4.com/wireguard-tools/tree/src/config.c#n259

By default, wg retries 15 times, delay time increased to 1.2x of last delay time on each failure.
This make wg keeps wait for around 1 minutes until dns resolved or all attempts failed.

BTW, the reset-wireguard.xml.in is simplified by following your tips, please check if there is anything else I didn't make right.
That was AI generated code, because my earlier attempts on completions were failed.

@sarthurdev
Copy link
Member

By default, wg retries 15 times, delay time increased to 1.2x of last delay time on each failure.
This make wg keeps wait for around 1 minutes until dns resolved or all attempts failed.

That's what I was concerned by, we want to avoid long boot/commit time because of DNS resolution not being available.

@sskaje
Copy link
Contributor Author

sskaje commented Nov 20, 2024

By default, wg retries 15 times, delay time increased to 1.2x of last delay time on each failure.
This make wg keeps wait for around 1 minutes until dns resolved or all attempts failed.

That's what I was concerned by, we want to avoid long boot/commit time because of DNS resolution not being available.

Yes, that's why I set it to 5 and I still feel it costs too much time.

How about 3x? Or 3x by default and let user can customize it in somewhere like set interfaces wireguard wg0 max-dns-retry?

@sskaje
Copy link
Contributor Author

sskaje commented Nov 21, 2024

max-dns-retry added, limit 1-15.

vyos@vyos# set interfaces wireguard wg0
Possible completions:
+  address              IP address
   description          Description
   disable              Administratively disable interface
   domain-name          Endpoint Domain Name
   fwmark               A 32-bit fwmark value set on all outgoing packets (default: 0)
 > ip                   IPv4 routing parameters
 > ipv6                 IPv6 routing parameters
   max-dns-retry        Max retry when DNS resolves failed. (default: 3)
 > mirror               Mirror ingress/egress packets
   mtu                  Maximum Transmission Unit (MTU) (default: 1420)
+> peer                 peer alias
   per-client-thread    Process traffic from each client in a dedicated thread
   port                 Port number used by connection
   private-key          Base64 encoded private key
   redirect             Redirect incoming packet to destination
   vrf                  VRF instance name


[edit]
vyos@vyos# set interfaces wireguard wg0 max-dns-retry
Possible completions:
   <1-15>               Max retry times



[edit]
vyos@vyos# set interfaces wireguard wg0 max-dns-retry 3
[edit]
vyos@vyos#

@sskaje
Copy link
Contributor Author

sskaje commented Nov 29, 2024

Hi reviewers,

Still need help:

  1. Exception handling in WireGuardIf.update(). print() or any best practice in vyos?
  2. vyos-domain-resolver part in interfaces_wireguard, need help about the logic: as I read what's in nat & firewall, the service will keep running if ip_fqdn or ip6_fqdn not empty. That means I should check all working peers and build a similar object storing peers with domain, and check if this object not empty to create the /run/use-vyos-domain-resolver-*?

@dmbaturin
Copy link
Member

The current design seems promising, we'll get back to you with comments about implementation if we find anything that we think we can improve.

python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
src/conf_mode/nat.py Show resolved Hide resolved
src/helpers/vyos-domain-resolver.py Outdated Show resolved Hide resolved
@c-po
Copy link
Member

c-po commented Dec 27, 2024

Hi @sskaje,

thanks for the PR! I did a testdrive with this implementation and in general it works for me.
Can you please address the open requested changes so we can get this merged?

Please also rebase your work on current as its pretty far behind.

@sskaje
Copy link
Contributor Author

sskaje commented Dec 27, 2024

I'm trying to build and run tests, but I see frr (>= 10.2) is now required because of T6746, but the latest 1.5-rolling-202412160007 doesn't have frr >= 10.2.

Any Idea how I build and run tests? Revert code in T6746 or just update debian/control?

=============

Updated: I modified debian/control on my VyOS VM.

@c-po
Copy link
Member

c-po commented Dec 28, 2024

FRR 10.2 is part of the official VyOS repositories now. You do not need to build it on your own.

You could grab a fresh build from e.g. https://github.com/vyos/vyos-nightly-build/actions/runs/12521976266/artifacts/2367044381

It contains some minor smoketest issues but would be fine for your development. If you're uncertain ant would rather NOT rebase that would work, too - we can rebase in the end after all changes got applied.

@c-po
Copy link
Member

c-po commented Dec 29, 2024

@sskaje you can also force package installation and omit the FRR 10.2 dependency

dpkg --install --force-all *.deb

@c-po c-po requested a review from sarthurdev December 29, 2024 09:05
python/vyos/ifconfig/wireguard.py Outdated Show resolved Hide resolved
@c-po
Copy link
Member

c-po commented Dec 30, 2024

I tested on my vm, when I move it back to ip address, the /run/use-vyos-domain-resolver-interfaces-wireguard-wgX is gone, and vyos-domain-resolver turn stopped here.

image

In you example vyos-domain-resolver is started again after you set interfaces wireguard wg2 peer wg2-test address x.x.x.x - I think this is invalid.

@c-po
Copy link
Member

c-po commented Dec 30, 2024

This branch has conflicts that must be resolved

  • src/helpers/vyos-domain-resolver.py

Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

Copy link

Conflicts have been resolved. A maintainer will review the pull request shortly.

@sskaje
Copy link
Contributor Author

sskaje commented Jan 12, 2025

Another thing, 'disable' is defined in wireguard interfaces but not checked when creating interfaces.

Should I also fix this?

@c-po
Copy link
Member

c-po commented Jan 13, 2025

Another thing, 'disable' is defined in wireguard interfaces but not checked when creating interfaces.

Should I also fix this?

This is actually expected behavior. The interface is always created, and disable will place it in up/down state.

e.g. ip link set dev wg up

This is handle here:

# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as
# often as 'down'. This is required by some interface implementations
# as certain parameters can only be changed when the interface is
# in admin-down state. This ensures the link does not flap during
# reconfiguration.
state = 'down' if 'disable' in config else 'up'
self.set_admin_state(state)

I will re-review and test the changes until end of this week.

@c-po
Copy link
Member

c-po commented Jan 18, 2025

@sskaje I've added a discrete smoketest test_wireguard_hostname for the feature and reworked the WireGuard tests in general to benefit from the global interface test framework

vyos@vyos:~$ /usr/libexec/vyos/tests/smoke/cli/test_interfaces_wireguard.py
test_add_multiple_ip_addresses (__main__.WireGuardInterfaceTest.test_add_multiple_ip_addresses) ... ok
test_add_single_ip_address (__main__.WireGuardInterfaceTest.test_add_single_ip_address) ... ok
test_add_to_invalid_vrf (__main__.WireGuardInterfaceTest.test_add_to_invalid_vrf) ... ok
test_dhcp_client_options (__main__.WireGuardInterfaceTest.test_dhcp_client_options) ... skipped 'not supported'
test_dhcp_disable_interface (__main__.WireGuardInterfaceTest.test_dhcp_disable_interface) ... skipped 'not supported'
test_dhcp_vrf (__main__.WireGuardInterfaceTest.test_dhcp_vrf) ... skipped 'not supported'
test_dhcpv6_client_options (__main__.WireGuardInterfaceTest.test_dhcpv6_client_options) ... skipped 'not supported'
test_dhcpv6_vrf (__main__.WireGuardInterfaceTest.test_dhcpv6_vrf) ... skipped 'not supported'
test_dhcpv6pd_auto_sla_id (__main__.WireGuardInterfaceTest.test_dhcpv6pd_auto_sla_id) ... skipped 'not supported'
test_dhcpv6pd_manual_sla_id (__main__.WireGuardInterfaceTest.test_dhcpv6pd_manual_sla_id) ... skipped 'not supported'
test_eapol (__main__.WireGuardInterfaceTest.test_eapol) ... skipped 'not supported'
test_interface_description (__main__.WireGuardInterfaceTest.test_interface_description) ... ok
test_interface_disable (__main__.WireGuardInterfaceTest.test_interface_disable) ... ok
test_interface_ip_options (__main__.WireGuardInterfaceTest.test_interface_ip_options) ... ok
test_interface_ipv6_options (__main__.WireGuardInterfaceTest.test_interface_ipv6_options) ... ok
test_interface_mtu (__main__.WireGuardInterfaceTest.test_interface_mtu) ... ok
test_ipv6_link_local_address (__main__.WireGuardInterfaceTest.test_ipv6_link_local_address) ... ok
test_move_interface_between_vrf_instances (__main__.WireGuardInterfaceTest.test_move_interface_between_vrf_instances) ... ok
test_mtu_1200_no_ipv6_interface (__main__.WireGuardInterfaceTest.test_mtu_1200_no_ipv6_interface) ... ok
test_span_mirror (__main__.WireGuardInterfaceTest.test_span_mirror) ... skipped 'not supported'
test_vif_8021q_interfaces (__main__.WireGuardInterfaceTest.test_vif_8021q_interfaces) ... skipped 'not supported'
test_vif_8021q_lower_up_down (__main__.WireGuardInterfaceTest.test_vif_8021q_lower_up_down) ... skipped 'not supported'
test_vif_8021q_mtu_limits (__main__.WireGuardInterfaceTest.test_vif_8021q_mtu_limits) ... skipped 'not supported'
test_vif_8021q_qos_change (__main__.WireGuardInterfaceTest.test_vif_8021q_qos_change) ... skipped 'not supported'
test_vif_s_8021ad_vlan_interfaces (__main__.WireGuardInterfaceTest.test_vif_s_8021ad_vlan_interfaces) ... skipped 'not supported'
test_vif_s_protocol_change (__main__.WireGuardInterfaceTest.test_vif_s_protocol_change) ... skipped 'not supported'
test_wireguard_add_remove_peer (__main__.WireGuardInterfaceTest.test_wireguard_add_remove_peer) ... ok
test_wireguard_hostname (__main__.WireGuardInterfaceTest.test_wireguard_hostname) ... ok
test_wireguard_peer (__main__.WireGuardInterfaceTest.test_wireguard_peer) ... ok
test_wireguard_peer_pubkey_change (__main__.WireGuardInterfaceTest.test_wireguard_peer_pubkey_change) ... ok
test_wireguard_same_public_key (__main__.WireGuardInterfaceTest.test_wireguard_same_public_key) ... ok
test_wireguard_threaded (__main__.WireGuardInterfaceTest.test_wireguard_threaded) ... ok

----------------------------------------------------------------------
Ran 32 tests in 270.296s

OK (skipped=15)

In addition I did some small refactorings and added a comment about the last handshake/rekey time.

WireGuard performs a handshake every WIREGUARD_REKEY_AFTER_TIME if data is
being transmitted between the peers. If no data is transmitted, the handshake
will not be initiated unless new data begins to flow. Each handshake generates
a new session key, and the key is rotated at least every 120 seconds or upon
data transmission after a prolonged silence.

This behavior raises a small question - is there a possibility to find out when the last byte/message was transmitted?
Imagine a "silent" WireGuard link that will be continously resetted as the handshake was not completed in the given time. Of course, one can enable persistent-keepalive on the given peer but that would be another option to enable.

sskaje and others added 9 commits January 19, 2025 00:17
* set interfaces wireguard wgXX peer YY hostname <fqdn>
WireGuard performs a handshake every WIREGUARD_REKEY_AFTER_TIME if data is
being transmitted between the peers. If no data is transmitted, the handshake
will not be initiated unless new data begins to flow. Each handshake generates
a new session key, and the key is rotated at least every 120 seconds or upon
data transmission after a prolonged silence.
Retrieving the config dict sets key_mangling(), thus we need to look for
host_name instead of host-name.
… queries

Extend ConfigTreeQuery().get_config_dict() with arguments to read in default
CLI values, too. This removes the need for hardcoded default values at
multiple places like:

if max_dns_retry is None:
    max_dns_retry = 3

in this case.
… method

Method is not referenced in the code base, remove dead code.
src/op_mode/reset_wireguard.py Outdated Show resolved Hide resolved
@c-po c-po removed the rebase label Jan 19, 2025
Copy link

CI integration 👍 passed!

Details

CI logs

  • CLI Smoketests (no interfaces) 👍 passed
  • CLI Smoketests (interfaces only) 👍 passed
  • Config tests 👍 passed
  • RAID1 tests 👍 passed
  • TPM tests 👍 passed

@sskaje
Copy link
Contributor Author

sskaje commented Jan 20, 2025

@sskaje I've added a discrete smoketest test_wireguard_hostname for the feature and reworked the WireGuard tests in general to benefit from the global interface test framework

vyos@vyos:~$ /usr/libexec/vyos/tests/smoke/cli/test_interfaces_wireguard.py
test_add_multiple_ip_addresses (__main__.WireGuardInterfaceTest.test_add_multiple_ip_addresses) ... ok
test_add_single_ip_address (__main__.WireGuardInterfaceTest.test_add_single_ip_address) ... ok
test_add_to_invalid_vrf (__main__.WireGuardInterfaceTest.test_add_to_invalid_vrf) ... ok
test_dhcp_client_options (__main__.WireGuardInterfaceTest.test_dhcp_client_options) ... skipped 'not supported'
test_dhcp_disable_interface (__main__.WireGuardInterfaceTest.test_dhcp_disable_interface) ... skipped 'not supported'
test_dhcp_vrf (__main__.WireGuardInterfaceTest.test_dhcp_vrf) ... skipped 'not supported'
test_dhcpv6_client_options (__main__.WireGuardInterfaceTest.test_dhcpv6_client_options) ... skipped 'not supported'
test_dhcpv6_vrf (__main__.WireGuardInterfaceTest.test_dhcpv6_vrf) ... skipped 'not supported'
test_dhcpv6pd_auto_sla_id (__main__.WireGuardInterfaceTest.test_dhcpv6pd_auto_sla_id) ... skipped 'not supported'
test_dhcpv6pd_manual_sla_id (__main__.WireGuardInterfaceTest.test_dhcpv6pd_manual_sla_id) ... skipped 'not supported'
test_eapol (__main__.WireGuardInterfaceTest.test_eapol) ... skipped 'not supported'
test_interface_description (__main__.WireGuardInterfaceTest.test_interface_description) ... ok
test_interface_disable (__main__.WireGuardInterfaceTest.test_interface_disable) ... ok
test_interface_ip_options (__main__.WireGuardInterfaceTest.test_interface_ip_options) ... ok
test_interface_ipv6_options (__main__.WireGuardInterfaceTest.test_interface_ipv6_options) ... ok
test_interface_mtu (__main__.WireGuardInterfaceTest.test_interface_mtu) ... ok
test_ipv6_link_local_address (__main__.WireGuardInterfaceTest.test_ipv6_link_local_address) ... ok
test_move_interface_between_vrf_instances (__main__.WireGuardInterfaceTest.test_move_interface_between_vrf_instances) ... ok
test_mtu_1200_no_ipv6_interface (__main__.WireGuardInterfaceTest.test_mtu_1200_no_ipv6_interface) ... ok
test_span_mirror (__main__.WireGuardInterfaceTest.test_span_mirror) ... skipped 'not supported'
test_vif_8021q_interfaces (__main__.WireGuardInterfaceTest.test_vif_8021q_interfaces) ... skipped 'not supported'
test_vif_8021q_lower_up_down (__main__.WireGuardInterfaceTest.test_vif_8021q_lower_up_down) ... skipped 'not supported'
test_vif_8021q_mtu_limits (__main__.WireGuardInterfaceTest.test_vif_8021q_mtu_limits) ... skipped 'not supported'
test_vif_8021q_qos_change (__main__.WireGuardInterfaceTest.test_vif_8021q_qos_change) ... skipped 'not supported'
test_vif_s_8021ad_vlan_interfaces (__main__.WireGuardInterfaceTest.test_vif_s_8021ad_vlan_interfaces) ... skipped 'not supported'
test_vif_s_protocol_change (__main__.WireGuardInterfaceTest.test_vif_s_protocol_change) ... skipped 'not supported'
test_wireguard_add_remove_peer (__main__.WireGuardInterfaceTest.test_wireguard_add_remove_peer) ... ok
test_wireguard_hostname (__main__.WireGuardInterfaceTest.test_wireguard_hostname) ... ok
test_wireguard_peer (__main__.WireGuardInterfaceTest.test_wireguard_peer) ... ok
test_wireguard_peer_pubkey_change (__main__.WireGuardInterfaceTest.test_wireguard_peer_pubkey_change) ... ok
test_wireguard_same_public_key (__main__.WireGuardInterfaceTest.test_wireguard_same_public_key) ... ok
test_wireguard_threaded (__main__.WireGuardInterfaceTest.test_wireguard_threaded) ... ok

----------------------------------------------------------------------
Ran 32 tests in 270.296s

OK (skipped=15)

In addition I did some small refactorings and added a comment about the last handshake/rekey time.

WireGuard performs a handshake every WIREGUARD_REKEY_AFTER_TIME if data is
being transmitted between the peers. If no data is transmitted, the handshake
will not be initiated unless new data begins to flow. Each handshake generates
a new session key, and the key is rotated at least every 120 seconds or upon
data transmission after a prolonged silence.

This behavior raises a small question - is there a possibility to find out when the last byte/message was transmitted? Imagine a "silent" WireGuard link that will be continously resetted as the handshake was not completed in the given time. Of course, one can enable persistent-keepalive on the given peer but that would be another option to enable.

I don't find anything related to 'when the last byte/message was transmitted' in both kernel and wg-tool, only WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL.

@c-po
Copy link
Member

c-po commented Jan 20, 2025

I don't find anything related to 'when the last byte/message was transmitted' in both kernel and wg-tool, only WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL.

We could add the following logic:

If (now_time - handshake_time > 3*WIREGUARD_REKEY_AFTER_TIME) is true and there was no rekey because no data is flowing, we should also check old vs new DNS name and only reset the interface if the resolved DNS name changed.

See vyos.firewall.fqdn_resolve(). What you think @sskaje

@sskaje
Copy link
Contributor Author

sskaje commented Jan 21, 2025

I don't find anything related to 'when the last byte/message was transmitted' in both kernel and wg-tool, only WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL.

We could add the following logic:

If (now_time - handshake_time > 3*WIREGUARD_REKEY_AFTER_TIME) is true and there was no rekey because no data is flowing, we should also check old vs new DNS name and only reset the interface if the resolved DNS name changed.

See vyos.firewall.fqdn_resolve(). What you think @sskaje

For the rekey time part, I tested on my router, one of my WG interface got rekey in about every 120-140 seconds.
3 * rekey time is enough.

For the DNS part, in fact, resetting peers do reset both address and port of peer endpoint.

I don't think you need to do resolve by python, just let wg do everything, otherwise you need to do domain resolve check, port check, too much work.

For port changing, I have two cases:

Case A: bad case, one of my friend's network, pppoe, he asked ISP for Internet IP, IP changes when pppoe reconnects. He asked me to set up connection between his network and his cloud server.

His ISP may have some rule like QoS for UDP traffics. I have to periodically change listen-port by wg set wg0 listen-port $PORT and also keep a fixed port and set up port forwarding to current listen port, so that another side can reset endpoint to its original address:port. <= port change

His router pppoe gets disconnected randomly every few weeks and Internet IP changes. <= ip change

I connect my router to his network, but I don't need a always online connection, in this case, I need to reset peer if my connection lost.

Case B: similar to case A, port changing policy was also used to "bypass" national firewall's ip+port traffic censorship, but both side have public ip and my ISP behaviors well.

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

Successfully merging this pull request may close these issues.

5 participants