Skip to content

Commit 0f6182e

Browse files
committed
fix(ns-ha): remove ha zone
The ha zone makes the configuration harder and requires an extra interface connected to a switch. It also raise the possibility to have a split-brain situation
1 parent c8fa4c4 commit 0f6182e

File tree

3 files changed

+41
-61
lines changed

3 files changed

+41
-61
lines changed

packages/ns-api/files/ns.ha

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import os
1414
import subprocess
1515
import hashlib
1616
import time
17-
from nethsec import firewall
17+
from nethsec import utils
1818
from jinja2 import Template
1919

2020
conntrack_ji_template = """
@@ -27,8 +27,8 @@ Sync {
2727
2828
UDP {
2929
# Dedicated link for connection replication
30-
IPv4_address {{ first_ip }}
31-
IPv4_Destination_Address {{ second_ip }}
30+
IPv4_address {{ main_ip }}
31+
IPv4_Destination_Address {{ backup_ip }}
3232
Port 3780
3333
Interface {{ ha_interface }}
3434
SndSocketBuffer 1249280
@@ -70,28 +70,21 @@ General {
7070
}
7171
"""
7272

73+
def get_device_from_ip(uci, ipaddr):
74+
for n in utils.get_all_by_type(uci, 'network', 'interface'):
75+
if uci.get('network', n, 'ipaddr', default=None) == ipaddr:
76+
return uci.get('network', n, 'device', default=None)
7377

74-
def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_secondary_ipaddress, pubkey = "", password = ""):
78+
79+
def setup(role, main_node_ip, backup_node_ip, virtual_ip, pubkey = "", password = ""):
7580
ret = {}
7681
u = EUci()
77-
78-
firewall.add_template_zone(u, 'ns_ha', link="keepalived/ha_address")
79-
80-
u.set('network', 'ha', 'interface')
81-
u.set('network', 'ha', 'proto', 'static')
82-
u.set('network', 'ha', 'netmask', '255.255.255.0')
83-
u.set('network', 'ha', 'device', ha_interface)
84-
if role == 'main':
85-
u.set('network', 'ha', 'ipaddr', ha_main_ipaddress)
82+
if role == "main":
83+
lan_device = get_device_from_ip(u, main_node_ip)
8684
else:
87-
u.set('network', 'ha', 'ipaddr', ha_secondary_ipaddress)
88-
u.save('network')
89-
90-
# setup UAM rule, if needed
91-
(z_name, z_config) = firewall.get_zone_by_name(u, 'ha')
92-
if 'ha' not in z_config.get('network', []):
93-
u.set('firewall', z_name, 'network', ['ha'])
94-
u.save('firewall')
85+
lan_device = get_device_from_ip(u, backup_node_ip)
86+
if lan_device is None:
87+
raise utils.ValidationError('main_node_ip', 'no_device_with_static_ip_found')
9588

9689
u.set('dropbear', 'ha_link', 'dropbear')
9790
u.set('dropbear', 'ha_link', 'Port', '65022')
@@ -100,9 +93,9 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
10093
u.save('dropbear')
10194

10295
u.set('keepalived', 'ha_address', 'ipaddress')
103-
u.set('keepalived', 'ha_address', 'name', f'{lan_interface}_ha')
96+
u.set('keepalived', 'ha_address', 'name', f'{lan_device}_ha')
10497
u.set('keepalived', 'ha_address', 'address', virtual_ip)
105-
u.set('keepalived', 'ha_address', 'device', lan_interface)
98+
u.set('keepalived', 'ha_address', 'device', lan_device)
10699
u.set('keepalived', 'ha_address', 'label_suffix', 'ha')
107100

108101
u.set('keepalived', 'ha_sync', 'vrrp_script')
@@ -112,8 +105,8 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
112105
u.set('keepalived', 'ha_sync', 'weight', '100')
113106

114107
u.set('keepalived', 'lan_track', 'track_interface')
115-
u.set('keepalived', 'lan_track', 'name', f'{lan_interface}_ha')
116-
u.set('keepalived', 'lan_track', 'value', lan_interface)
108+
u.set('keepalived', 'lan_track', 'name', f'{lan_device}_ha')
109+
u.set('keepalived', 'lan_track', 'value', lan_device)
117110
u.set('keepalived', 'lan_track', 'weight', '100')
118111

119112
if role == 'main':
@@ -124,7 +117,7 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
124117

125118
u.set('keepalived', 'ha_peer', 'peer')
126119
u.set('keepalived', 'ha_peer', 'name', 'backup')
127-
u.set('keepalived', 'ha_peer', 'address', ha_secondary_ipaddress)
120+
u.set('keepalived', 'ha_peer', 'address', backup_node_ip)
128121
u.set('keepalived', 'ha_peer', 'sync', '1')
129122
u.set('keepalived', 'ha_peer', 'sync_mode', 'send')
130123
u.set('keepalived', 'ha_peer', 'sync_dir', '/usr/share/keepalived/rsync')
@@ -134,13 +127,13 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
134127
u.set('keepalived', 'main', 'vrrp_instance')
135128
u.set('keepalived', 'main', 'name', 'master')
136129
u.set('keepalived', 'main', 'state', 'MASTER')
137-
u.set('keepalived', 'main', 'interface', ha_interface)
130+
u.set('keepalived', 'main', 'interface', lan_device)
138131
u.set('keepalived', 'main', 'virtual_router_id', '100')
139132
u.set('keepalived', 'main', 'priority', '100')
140133
u.set('keepalived', 'main', 'advert_int', '1')
141134
u.set('keepalived', 'main', 'nopreempt', '0')
142-
u.set('keepalived', 'main', 'virtual_ipaddress', [f'{lan_interface}_ha'])
143-
u.set('keepalived', 'main', 'unicast_src_ip', ha_main_ipaddress)
135+
u.set('keepalived', 'main', 'virtual_ipaddress', [f'{lan_device}_ha'])
136+
u.set('keepalived', 'main', 'unicast_src_ip', main_node_ip)
144137
u.set('keepalived', 'main', 'unicast_peer', ['backup'])
145138
u.set('keepalived', 'main', 'auth_type', 'PASS')
146139

@@ -151,13 +144,13 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
151144
password = hashlib.sha1(current_time).hexdigest()[:8]
152145
ret['password'] = password
153146
u.set('keepalived', 'main', 'auth_pass', password)
154-
u.set('keepalived', 'main', 'track_interface', [f'{lan_interface}_ha'])
147+
u.set('keepalived', 'main', 'track_interface', [f'{lan_device}_ha'])
155148
u.set('keepalived', 'main', 'track_script', ['sender'])
156149

157150
# Generate the private key if it does not exist
158151
private_key_path = '/etc/keepalived/keys/id_rsa'
159152
if not os.path.isfile(private_key_path):
160-
subprocess.run(['dropbearkey', '-t', 'rsa', '-f', private_key_path])
153+
subprocess.run(['dropbearkey', '-t', 'rsa', '-f', private_key_path], capture_output=True)
161154

162155
# Print the public key
163156
result = subprocess.run(['dropbearkey', '-y', '-f', private_key_path], capture_output=True, text=True)
@@ -167,7 +160,7 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
167160

168161
# Setup conntrackd configuration
169162
conntrack_template = Template(conntrack_ji_template)
170-
conntrack_conf = conntrack_template.render(first_ip=ha_main_ipaddress, second_ip=ha_secondary_ipaddress, ha_interface=ha_interface)
163+
conntrack_conf = conntrack_template.render(main_ip=main_node_ip, backup_ip=backup_node_ip, ha_interface=lan_device)
171164
with open('/etc/conntrackd/conntrackd.conf', 'w') as file:
172165
file.write(conntrack_conf)
173166
else:
@@ -178,7 +171,7 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
178171

179172
u.set('keepalived', 'ha_peer', 'peer')
180173
u.set('keepalived', 'ha_peer', 'name', 'master')
181-
u.set('keepalived', 'ha_peer', 'address', ha_main_ipaddress)
174+
u.set('keepalived', 'ha_peer', 'address', main_node_ip)
182175
u.set('keepalived', 'ha_peer', 'sync', '1')
183176
u.set('keepalived', 'ha_peer', 'sync_mode', 'receive')
184177
u.set('keepalived', 'ha_peer', 'sync_dir', '/usr/share/keepalived/rsync')
@@ -187,17 +180,17 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
187180
u.set('keepalived', 'backup', 'vrrp_instance')
188181
u.set('keepalived', 'backup', 'name', 'backup')
189182
u.set('keepalived', 'backup', 'state', 'BACKUP')
190-
u.set('keepalived', 'backup', 'interface', ha_interface)
183+
u.set('keepalived', 'backup', 'interface', lan_device)
191184
u.set('keepalived', 'backup', 'virtual_router_id', '100')
192185
u.set('keepalived', 'backup', 'priority', '50')
193186
u.set('keepalived', 'backup', 'advert_int', '1')
194187
u.set('keepalived', 'backup', 'nopreempt', '0')
195-
u.set('keepalived', 'backup', 'virtual_ipaddress', [f'{lan_interface}_ha'])
196-
u.set('keepalived', 'backup', 'unicast_src_ip', ha_secondary_ipaddress)
188+
u.set('keepalived', 'backup', 'virtual_ipaddress', [f'{lan_device}_ha'])
189+
u.set('keepalived', 'backup', 'unicast_src_ip', backup_node_ip)
197190
u.set('keepalived', 'backup', 'unicast_peer', ['master'])
198191
u.set('keepalived', 'backup', 'auth_type', 'PASS')
199192
u.set('keepalived', 'backup', 'auth_pass', password)
200-
u.set('keepalived', 'backup', 'track_interface', [f'{lan_interface}_ha'])
193+
u.set('keepalived', 'backup', 'track_interface', [f'{lan_device}_ha'])
201194
u.set('keepalived', 'backup', 'track_script', ['receiver'])
202195

203196
# Append publick key to root dropbear authorized_keys
@@ -212,7 +205,7 @@ def setup(role, lan_interface, ha_interface, virtual_ip, ha_main_ipaddress, ha_s
212205

213206
# Setup conntrackd configuration
214207
conntrack_template = Template(conntrack_ji_template)
215-
conntrack_conf = conntrack_template.render(first_ip=ha_secondary_ipaddress, second_ip=ha_main_ipaddress, ha_interface=ha_interface)
208+
conntrack_conf = conntrack_template.render(first_ip=main_node_ip, second_ip=backup_node_ip, ha_interface=lan_device)
216209
with open('/etc/conntrackd/conntrackd.conf', 'w') as file:
217210
file.write(conntrack_conf)
218211

@@ -250,11 +243,9 @@ cmd = sys.argv[1]
250243
if cmd == 'list':
251244
print(json.dumps({"setup": {
252245
"role": "main",
253-
"lan_interface": "eth0",
254-
"ha_interface": "eth1",
246+
"main_node_ip": "100.100.100.1",
247+
"backup_node_ip": "100.100.100.2",
255248
"virtual_ip": "192.168.1.1",
256-
"ha_main_ipaddress": "100.100.100.1",
257-
"ha_secondary_ipaddress": "100.100.100.2",
258249
"pubkey": "ssh-rsa AAAAB....",
259250
"password": "admin"
260251
},
@@ -265,7 +256,7 @@ else:
265256
if action == "setup":
266257
# Paramaters:
267258
args = json.loads(sys.stdin.read())
268-
ret = setup(args.get('role'), args.get('lan_interface'), args.get('ha_interface'), args.get('virtual_ip'), args.get('ha_main_ipaddress'), args.get('ha_secondary_ipaddress'), args.get('pubkey'), args.get('password'))
259+
ret = setup(args.get('role'), args.get('main_node_ip'), args.get('backup_node_ip'), args.get('virtual_ip'), args.get('pubkey'), args.get('password'))
269260
elif action == "status":
270261
ret = status()
271262

packages/ns-api/files/templates

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,6 @@ config template_rule 'ns_guest_dhcp'
112112
option proto 'udp'
113113
option target 'ACCEPT'
114114

115-
# HA zone
116-
117-
config template_zone 'ns_ha'
118-
option name 'ha'
119-
option input 'ACCEPT'
120-
option output 'ACCEPT'
121-
option forward 'ACCEPT'
122-
option ns_description 'HA zone for cluster traffic'
123-
124115
# IPSec
125116

126117
config template_rule 'ns_ipsec_esp'

packages/ns-ha/README.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Requirements:
77

88
- Two nodes with similar hardware
99
- Nodes must be connected to the same LAN
10-
- Nodes must have a dedicated interface for HA configuration
1110
- Nodes must have only one WAN interface configured with DHCP
1211

1312
Limitations:
@@ -48,16 +47,15 @@ The following features are supported:
4847
## Configuration
4948

5049
The setup process configures the following:
51-
- Creates a new firewall zone `ha`
52-
- Configures the HA interface dedicated to HA traffic
50+
- Configures HA traffic on lan interface
5351
- Sets up keepalived with the virtual IP, a random password and a public key for the synchronization
5452
- Configures dropbear to listen on port `65022`: this is used to sync data between the nodes using rsync, only
5553
key-based authentication is allowed
5654
- Configures conntrackd to sync the connection tracking table
5755

5856
In this example:
5957
- `main` is the primary node, with LAN IP `192.168.100.238` and HA IP `10.12.12.1`
60-
- `secondary` is the secondary node, with LAN IP `192.168.100.237` and HA IP `10.12.12.2`
58+
- `backup` is the backup node, with LAN IP `192.168.100.237` and HA IP `10.12.12.2`
6159
- the virtual IP is `192.168.100.240`
6260

6361
1. On the primary node:
@@ -67,13 +65,13 @@ In this example:
6765
- Reserve `eth2` for HA configuration (it must not configured in the network settings)
6866
- Setup the configuration that will: create the `ha` zone, configure the IP for the HA interface, setup keepalived:
6967
```sh
70-
echo '{"role": "main", "lan_interface": "br-lan", "ha_interface": "eth2", "virtual_ip": "192.168.100.240/24", "ha_main_ipaddress": "10.12.12.1", "ha_secondary_ipaddress": "10.12.12.2"}' | /usr/libexec/rpcd/ns.ha call setup
68+
echo '{"role": "main", "main_node_ip": "192.168.100.238", "backup_node_ip": "192.168.100.239", "virtual_ip": "192.168.100.240/24"}' | /usr/libexec/rpcd/ns.ha call setup
7169
```
7270
The command will output something like:
7371
```json
7472
{"password": "5aeab1d8", "pubkey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDF7MYY8vfgE/JgJT8mOejwIhB4UYKS4g/QSA7fwntCbN0LQ3nTA6LO3AzqhUCHd6LBS5P9aefTqDcG+cJQiGbXReqX1z4trQGs7QkBLbjlXb2Vock17UIGbm5ao8jyPsD4ADNdMF8p0S2xDvnfsOh7MXLy5N7QZGp1G3ISB6JVw0mdCn3GXYg1X9XB7Pqu0OJm7+n2SJvA1KXn9fKUDX92U1fGQcid05C3yRBS5QXB7VAAP55KKYp4RmQMCOcJDhDoHGB6Ia/fTxfhnLdXJcAHU2MTtyaEY7NWoPjKZ3769GIu4KLLDPB8aH9emg23Mej+eiMRIg0vFXsaJWVPuZzj root@primary"}
7573
```
76-
The `password` and `pubkey` fields must be used in the secondary node configuration.
74+
The `password` and `pubkey` fields must be used in the backup node configuration.
7775
- Apply the configuration:
7876
```
7977
uci commit
@@ -82,14 +80,14 @@ In this example:
8280
/etc/init.d/keepalived restart
8381
```
8482

85-
2. On the secondary node:
86-
- Name the secondary firewall `secondary`
83+
2. On the backup node:
84+
- Name the backup firewall `backup`
8785
- Set `eth0` (LAN) to static IP: `192.168.100.237/24`
8886
- Set `eth1` (WAN) to DHCP (no PPPoE)
8987
- The `eth2` interface will be used for the HA configuration
9088
- Setup the configuration that will: create the `ha` zone, configure the IP for the HA interface, setup keepalived. Use the `password` and `pubkey` from the primary node:
9189
```sh
92-
echo '{"role": "secondary", "lan_interface": "br-lan", "ha_interface": "eth2", "virtual_ip": "192.168.100.240/24", "ha_main_ipaddress": "10.12.12.1", "ha_secondary_ipaddress": "10.12.12.2", "password": "5aeab1d8", "pubkey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDF7MYY8vfgE/JgJT8mOejwIhB4UYKS4g/QSA7fwntCbN0LQ3nTA6LO3AzqhUCHd6LBS5P9aefTqDcG+cJQiGbXReqX1z4trQGs7QkBLbjlXb2Vock17UIGbm5ao8jyPsD4ADNdMF8p0S2xDvnfsOh7MXLy5N7QZGp1G3ISB6JVw0mdCn3GXYg1X9XB7Pqu0OJm7+n2SJvA1KXn9fKUDX92U1fGQcid05C3yRBS5QXB7VAAP55KKYp4RmQMCOcJDhDoHGB6Ia/fTxfhnLdXJcAHU2MTtyaEY7NWoPjKZ3769GIu4KLLDPB8aH9emg23Mej+eiMRIg0vFXsaJWVPuZzj root@primary"}' | /usr/libexec/rpcd/ns.ha call setup
90+
echo '{"role": "backup", "main_node_ip": "192.168.100.238", "backup_node_ip": "192.168.100.239", "virtual_ip": "192.168.100.240/24", "password": "5aeab1d8", "pubkey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDF7MYY8vfgE/JgJT8mOejwIhB4UYKS4g/QSA7fwntCbN0LQ3nTA6LO3AzqhUCHd6LBS5P9aefTqDcG+cJQiGbXReqX1z4trQGs7QkBLbjlXb2Vock17UIGbm5ao8jyPsD4ADNdMF8p0S2xDvnfsOh7MXLy5N7QZGp1G3ISB6JVw0mdCn3GXYg1X9XB7Pqu0OJm7+n2SJvA1KXn9fKUDX92U1fGQcid05C3yRBS5QXB7VAAP55KKYp4RmQMCOcJDhDoHGB6Ia/fTxfhnLdXJcAHU2MTtyaEY7NWoPjKZ3769GIu4KLLDPB8aH9emg23Mej+eiMRIg0vFXsaJWVPuZzj root@primary"}' | /usr/libexec/rpcd/ns.ha call setup
9391
uci commit
9492
/etc/init.d/network restart
9593
/etc/init.d/firewall restart

0 commit comments

Comments
 (0)