Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F119062597
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
17 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index e0d204a0a..f6417764a 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -1,473 +1,475 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import re
import sys
import typing
from collections import OrderedDict
from hurry import filesize
from re import split as re_split
from tabulate import tabulate
from subprocess import TimeoutExpired
from vyos.util import call
from vyos.util import convert_data
from vyos.util import seconds_to_human
import vyos.opmode
SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
def _convert(text):
return int(text) if text.isdigit() else text.lower()
def _alphanum_key(key):
return [_convert(c) for c in re_split('([0-9]+)', str(key))]
def _get_vici_sas():
from vici import Session as vici_session
try:
session = vici_session()
except Exception:
raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
sas = list(session.list_sas())
return sas
def _get_raw_data_sas():
get_sas = _get_vici_sas()
sas = convert_data(get_sas)
return sas
def _get_formatted_output_sas(sas):
sa_data = []
for sa in sas:
for parent_sa in sa.values():
# create an item for each child-sa
for child_sa in parent_sa.get('child-sas', {}).values():
# prepare a list for output data
sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A'
# collect raw data
sa_name = child_sa.get('name')
sa_state = child_sa.get('state')
sa_uptime = child_sa.get('install-time')
sa_bytes_in = child_sa.get('bytes-in')
sa_bytes_out = child_sa.get('bytes-out')
sa_packets_in = child_sa.get('packets-in')
sa_packets_out = child_sa.get('packets-out')
sa_remote_addr = parent_sa.get('remote-host')
sa_remote_id = parent_sa.get('remote-id')
sa_proposal_encr_alg = child_sa.get('encr-alg')
sa_proposal_integ_alg = child_sa.get('integ-alg')
sa_proposal_encr_keysize = child_sa.get('encr-keysize')
sa_proposal_dh_group = child_sa.get('dh-group')
# format data to display
if sa_name:
sa_out_name = sa_name
if sa_state:
if sa_state == 'INSTALLED':
sa_out_state = 'up'
else:
sa_out_state = 'down'
if sa_uptime:
sa_out_uptime = seconds_to_human(sa_uptime)
if sa_bytes_in and sa_bytes_out:
bytes_in = filesize.size(int(sa_bytes_in))
bytes_out = filesize.size(int(sa_bytes_out))
sa_out_bytes = f'{bytes_in}/{bytes_out}'
if sa_packets_in and sa_packets_out:
packets_in = filesize.size(int(sa_packets_in),
system=filesize.si)
packets_out = filesize.size(int(sa_packets_out),
system=filesize.si)
packets_str = f'{packets_in}/{packets_out}'
sa_out_packets = re.sub(r'B', r'', packets_str)
if sa_remote_addr:
sa_out_remote_addr = sa_remote_addr
if sa_remote_id:
sa_out_remote_id = sa_remote_id
# format proposal
if sa_proposal_encr_alg:
sa_out_proposal = sa_proposal_encr_alg
if sa_proposal_encr_keysize:
sa_proposal_encr_keysize_str = sa_proposal_encr_keysize
sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}'
if sa_proposal_integ_alg:
sa_proposal_integ_alg_str = sa_proposal_integ_alg
sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}'
if sa_proposal_dh_group:
sa_proposal_dh_group_str = sa_proposal_dh_group
sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}'
# add a new item to output data
sa_data.append([
sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes,
sa_out_packets, sa_out_remote_addr, sa_out_remote_id,
sa_out_proposal
])
headers = [
"Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out",
"Remote address", "Remote ID", "Proposal"
]
sa_data = sorted(sa_data, key=_alphanum_key)
output = tabulate(sa_data, headers)
return output
# Connections block
def _get_vici_connections():
from vici import Session as vici_session
try:
session = vici_session()
except Exception:
raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
connections = list(session.list_conns())
return connections
def _get_convert_data_connections():
get_connections = _get_vici_connections()
connections = convert_data(get_connections)
return connections
def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
"""Get parent SA proposals by connection name
if connections not in the 'down' state
Args:
connection_name (str): Connection name
data (list): List of current SAs from vici
Returns:
str: Parent SA connection proposal
AES_CBC/256/HMAC_SHA2_256_128/MODP_1024
"""
if not data:
return {}
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return {}
+ continue
if 'encr-alg' in sa[connection_name]:
encr_alg = sa.get(connection_name, '').get('encr-alg')
cipher = encr_alg.split('_')[0]
mode = encr_alg.split('_')[1]
encr_keysize = sa.get(connection_name, '').get('encr-keysize')
integ_alg = sa.get(connection_name, '').get('integ-alg')
# prf_alg = sa.get(connection_name, '').get('prf-alg')
dh_group = sa.get(connection_name, '').get('dh-group')
proposal = {
'cipher': cipher,
'mode': mode,
'key_size': encr_keysize,
'hash': integ_alg,
'dh': dh_group
}
return proposal
return {}
def _get_parent_sa_state(connection_name: str, data: list) -> str:
"""Get parent SA state by connection name
Args:
connection_name (str): Connection name
data (list): List of current SAs from vici
Returns:
Parent SA connection state
"""
+ ike_state = 'down'
if not data:
- return 'down'
+ return ike_state
for sa in data:
# check if parent SA exist
- if connection_name not in sa.keys():
- return 'down'
- if sa[connection_name]['state'].lower() == 'established':
- return 'up'
- else:
- return 'down'
+ for connection, connection_conf in sa.items():
+ if connection_name != connection:
+ continue
+ if connection_conf['state'].lower() == 'established':
+ ike_state = 'up'
+ return ike_state
def _get_child_sa_state(connection_name: str, tunnel_name: str,
data: list) -> str:
"""Get child SA state by connection and tunnel name
Args:
connection_name (str): Connection name
tunnel_name (str): Tunnel name
data (list): List of current SAs from vici
Returns:
str: `up` if child SA state is 'installed' otherwise `down`
"""
+ child_sa = 'down'
if not data:
- return 'down'
+ return child_sa
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return 'down'
+ continue
child_sas = sa[connection_name]['child-sas']
# Get all child SA states
# there can be multiple SAs per tunnel
child_sa_states = [
v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name
]
- return 'up' if 'INSTALLED' in child_sa_states else 'down'
+ return 'up' if 'INSTALLED' in child_sa_states else child_sa
def _get_child_sa_info(connection_name: str, tunnel_name: str,
data: list) -> dict:
"""Get child SA installed info by connection and tunnel name
Args:
connection_name (str): Connection name
tunnel_name (str): Tunnel name
data (list): List of current SAs from vici
Returns:
dict: Info of the child SA in the dictionary format
"""
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return {}
+ continue
child_sas = sa[connection_name]['child-sas']
# Get all child SA data
# Skip temp SA name (first key), get only SA values as dict
# {'OFFICE-B-tunnel-0-46': {'name': 'OFFICE-B-tunnel-0'}...}
# i.e get all data after 'OFFICE-B-tunnel-0-46'
child_sa_info = [
v for k, v in child_sas.items() if 'name' in v and
v['name'] == tunnel_name and v['state'] == 'INSTALLED'
]
return child_sa_info[-1] if child_sa_info else {}
def _get_child_sa_proposal(child_sa_data: dict) -> dict:
if child_sa_data and 'encr-alg' in child_sa_data:
encr_alg = child_sa_data.get('encr-alg')
cipher = encr_alg.split('_')[0]
mode = encr_alg.split('_')[1]
key_size = child_sa_data.get('encr-keysize')
integ_alg = child_sa_data.get('integ-alg')
dh_group = child_sa_data.get('dh-group')
proposal = {
'cipher': cipher,
'mode': mode,
'key_size': key_size,
'hash': integ_alg,
'dh': dh_group
}
return proposal
return {}
def _get_raw_data_connections(list_connections: list, list_sas: list) -> list:
"""Get configured VPN IKE connections and IPsec states
Args:
list_connections (list): List of configured connections from vici
list_sas (list): List of current SAs from vici
Returns:
list: List and status of IKE/IPsec connections/tunnels
"""
base_dict = []
for connections in list_connections:
base_list = {}
for connection, conn_conf in connections.items():
base_list['ike_connection_name'] = connection
base_list['ike_connection_state'] = _get_parent_sa_state(
connection, list_sas)
base_list['ike_remote_address'] = conn_conf['remote_addrs']
base_list['ike_proposal'] = _get_parent_sa_proposal(
connection, list_sas)
base_list['local_id'] = conn_conf.get('local-1', '').get('id')
base_list['remote_id'] = conn_conf.get('remote-1', '').get('id')
base_list['version'] = conn_conf.get('version', 'IKE')
base_list['children'] = []
children = conn_conf['children']
for tunnel, tun_options in children.items():
state = _get_child_sa_state(connection, tunnel, list_sas)
local_ts = tun_options.get('local-ts')
remote_ts = tun_options.get('remote-ts')
dpd_action = tun_options.get('dpd_action')
close_action = tun_options.get('close_action')
sa_info = _get_child_sa_info(connection, tunnel, list_sas)
esp_proposal = _get_child_sa_proposal(sa_info)
base_list['children'].append({
'name': tunnel,
'state': state,
'local_ts': local_ts,
'remote_ts': remote_ts,
'dpd_action': dpd_action,
'close_action': close_action,
'sa': sa_info,
'esp_proposal': esp_proposal
})
base_dict.append(base_list)
return base_dict
def _get_raw_connections_summary(list_conn, list_sas):
import jmespath
data = _get_raw_data_connections(list_conn, list_sas)
match = '[*].children[]'
child = jmespath.search(match, data)
tunnels_down = len([k for k in child if k['state'] == 'down'])
tunnels_up = len([k for k in child if k['state'] == 'up'])
tun_dict = {
'tunnels': child,
'total': len(child),
'down': tunnels_down,
'up': tunnels_up
}
return tun_dict
def _get_formatted_output_conections(data):
from tabulate import tabulate
data_entries = ''
connections = []
for entry in data:
tunnels = []
ike_name = entry['ike_connection_name']
ike_state = entry['ike_connection_state']
conn_type = entry.get('version', 'IKE')
remote_addrs = ','.join(entry['ike_remote_address'])
local_ts, remote_ts = '-', '-'
local_id = entry['local_id']
remote_id = entry['remote_id']
proposal = '-'
if entry.get('ike_proposal'):
proposal = (f'{entry["ike_proposal"]["cipher"]}_'
f'{entry["ike_proposal"]["mode"]}/'
f'{entry["ike_proposal"]["key_size"]}/'
f'{entry["ike_proposal"]["hash"]}/'
f'{entry["ike_proposal"]["dh"]}')
connections.append([
ike_name, ike_state, conn_type, remote_addrs, local_ts, remote_ts,
local_id, remote_id, proposal
])
for tun in entry['children']:
tun_name = tun.get('name')
tun_state = tun.get('state')
conn_type = 'IPsec'
local_ts = '\n'.join(tun.get('local_ts'))
remote_ts = '\n'.join(tun.get('remote_ts'))
proposal = '-'
if tun.get('esp_proposal'):
proposal = (f'{tun["esp_proposal"]["cipher"]}_'
f'{tun["esp_proposal"]["mode"]}/'
f'{tun["esp_proposal"]["key_size"]}/'
f'{tun["esp_proposal"]["hash"]}/'
f'{tun["esp_proposal"]["dh"]}')
connections.append([
tun_name, tun_state, conn_type, remote_addrs, local_ts,
remote_ts, local_id, remote_id, proposal
])
connection_headers = [
'Connection', 'State', 'Type', 'Remote address', 'Local TS',
'Remote TS', 'Local id', 'Remote id', 'Proposal'
]
output = tabulate(connections, connection_headers, numalign='left')
return output
# Connections block end
def get_peer_connections(peer, tunnel):
search = rf'^[\s]*({peer}-(tunnel-[\d]+|vti)).*'
matches = []
if not os.path.exists(SWANCTL_CONF):
raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
suffix = None if tunnel is None else (f'tunnel-{tunnel}' if
tunnel.isnumeric() else tunnel)
with open(SWANCTL_CONF, 'r') as f:
for line in f.readlines():
result = re.match(search, line)
if result:
if tunnel is None:
matches.append(result[1])
else:
if result[2] == suffix:
matches.append(result[1])
return matches
def reset_peer(peer: str, tunnel:typing.Optional[str]):
conns = get_peer_connections(peer, tunnel)
if not conns:
raise vyos.opmode.IncorrectValue('Peer or tunnel(s) not found, aborting')
for conn in conns:
try:
call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10)
except TimeoutExpired as e:
raise vyos.opmode.InternalError(f'Timed out while resetting {conn}')
print('Peer reset result: success')
def show_sa(raw: bool):
sa_data = _get_raw_data_sas()
if raw:
return sa_data
return _get_formatted_output_sas(sa_data)
def show_connections(raw: bool):
list_conns = _get_convert_data_connections()
list_sas = _get_raw_data_sas()
if raw:
return _get_raw_data_connections(list_conns, list_sas)
connections = _get_raw_data_connections(list_conns, list_sas)
return _get_formatted_output_conections(connections)
def show_connections_summary(raw: bool):
list_conns = _get_convert_data_connections()
list_sas = _get_raw_data_sas()
if raw:
return _get_raw_connections_summary(list_conns, list_sas)
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Nov 6, 7:03 AM (19 h, 24 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4652112
Default Alt Text
(17 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment