Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F118305520
ipsec.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
16 KB
Referenced Files
None
Subscribers
None
ipsec.py
View Options
#!/usr/bin/env python3
#
# Copyright (C) 2022 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
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
{}
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
"""
if
not
data
:
return
'down'
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'
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`
"""
if
not
data
:
return
'down'
for
sa
in
data
:
# check if parent SA exist
if
connection_name
not
in
sa
.
keys
():
return
'down'
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'
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
{}
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
,
return_all
=
False
):
search
=
rf
'^[\s]*({peer}-(tunnel-[\d]+|vti)).*'
matches
=
[]
if
not
os
.
path
.
exists
(
SWANCTL_CONF
):
raise
vyos
.
opmode
.
UnconfiguredSubsystem
(
"IPsec not initialized"
)
with
open
(
SWANCTL_CONF
,
'r'
)
as
f
:
for
line
in
f
.
readlines
():
result
=
re
.
match
(
search
,
line
)
if
result
:
suffix
=
f
'tunnel-{tunnel}'
if
tunnel
.
isnumeric
()
else
tunnel
if
return_all
or
(
result
[
2
]
==
suffix
):
matches
.
append
(
result
[
1
])
return
matches
def
reset_peer
(
peer
:
str
,
tunnel
:
str
):
conns
=
get_peer_connections
(
peer
,
tunnel
,
return_all
=
(
not
tunnel
or
tunnel
==
'all'
))
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-script.python
Expires
Sat, Oct 17, 12:04 AM (17 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4379414
Default Alt Text
ipsec.py (16 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment