Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F118309925
policy_route.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
policy_route.py
View Options
#!/usr/bin/env python3
#
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
from
json
import
loads
from
sys
import
exit
from
vyos.base
import
Warning
from
vyos.config
import
Config
from
vyos.configdict
import
node_changed
from
vyos.configdiff
import
Diff
from
vyos.template
import
render
from
vyos.utils.dict
import
dict_search_args
from
vyos.utils.dict
import
dict_search_recursive
from
vyos.utils.process
import
cmd
from
vyos.utils.process
import
run
from
vyos.utils.network
import
get_vrf_tableid
from
vyos.defaults
import
rt_global_table
from
vyos.defaults
import
rt_global_vrf
from
vyos.geoip
import
geoip_refresh
,
geoip_update
from
vyos
import
ConfigError
from
vyos
import
airbag
airbag
.
enable
()
mark_offset
=
0x7FFFFFFF
nftables_conf
=
'/run/nftables_policy.conf'
valid_groups
=
[
'address_group'
,
'domain_group'
,
'network_group'
,
'port_group'
,
'interface_group'
]
def
geoip_updated
(
conf
):
updated
=
False
changes_v4
=
node_changed
(
conf
,
[
'policy'
,
'route'
],
key_mangling
=
(
'-'
,
'_'
),
recursive
=
True
,
expand_nodes
=
Diff
.
ADD
|
Diff
.
DELETE
)
for
_
,
path
in
dict_search_recursive
(
changes_v4
,
'geoip'
):
updated
=
True
break
if
not
updated
:
changes_v6
=
node_changed
(
conf
,
[
'policy'
,
'route6'
],
key_mangling
=
(
'-'
,
'_'
),
recursive
=
True
,
expand_nodes
=
Diff
.
ADD
|
Diff
.
DELETE
)
for
_
,
path
in
dict_search_recursive
(
changes_v6
,
'geoip'
):
updated
=
True
break
return
updated
def
geoip_sets
(
policy
):
out
=
{
'name'
:
[],
'ipv6_name'
:
[]}
for
_
,
path
in
dict_search_recursive
(
policy
,
'geoip'
):
if
(
path
[
0
]
==
'route'
):
out
[
'name'
]
.
append
(
f
'GEOIP_CC_{path[0]}_{path[1]}_{path[3]}'
)
elif
(
path
[
0
]
==
'route6'
):
out
[
'ipv6_name'
]
.
append
(
f
'GEOIP_CC6_{path[0]}_{path[1]}_{path[3]}'
)
return
out
def
get_config
(
config
=
None
):
if
config
:
conf
=
config
else
:
conf
=
Config
()
base
=
[
'policy'
]
policy
=
conf
.
get_config_dict
(
base
,
key_mangling
=
(
'-'
,
'_'
),
get_first_key
=
True
,
no_tag_node_value_mangle
=
True
)
policy
[
'firewall_group'
]
=
conf
.
get_config_dict
([
'firewall'
,
'group'
],
key_mangling
=
(
'-'
,
'_'
),
get_first_key
=
True
,
no_tag_node_value_mangle
=
True
)
# Remove dynamic firewall groups if present:
if
'dynamic_group'
in
policy
[
'firewall_group'
]:
del
policy
[
'firewall_group'
][
'dynamic_group'
]
policy
[
'geoip_sets'
]
=
geoip_sets
(
policy
)
policy
[
'geoip_updated'
]
=
geoip_updated
(
conf
)
return
policy
def
verify_rule
(
policy
,
name
,
rule_conf
,
ipv6
,
rule_id
):
icmp
=
'icmp'
if
not
ipv6
else
'icmpv6'
if
icmp
in
rule_conf
:
icmp_defined
=
False
if
'type_name'
in
rule_conf
[
icmp
]:
icmp_defined
=
True
if
'code'
in
rule_conf
[
icmp
]
or
'type'
in
rule_conf
[
icmp
]:
raise
ConfigError
(
f
'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name'
)
if
'code'
in
rule_conf
[
icmp
]:
icmp_defined
=
True
if
'type'
not
in
rule_conf
[
icmp
]:
raise
ConfigError
(
f
'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined'
)
if
'type'
in
rule_conf
[
icmp
]:
icmp_defined
=
True
if
icmp_defined
and
'protocol'
not
in
rule_conf
or
rule_conf
[
'protocol'
]
!=
icmp
:
raise
ConfigError
(
f
'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP'
)
if
'set'
in
rule_conf
:
if
'tcp_mss'
in
rule_conf
[
'set'
]:
tcp_flags
=
dict_search_args
(
rule_conf
,
'tcp'
,
'flags'
)
if
not
tcp_flags
or
'syn'
not
in
tcp_flags
:
raise
ConfigError
(
f
'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS'
)
if
'vrf'
in
rule_conf
[
'set'
]
and
'table'
in
rule_conf
[
'set'
]:
raise
ConfigError
(
f
'{name} rule {rule_id}: Cannot set both forwarding route table and VRF'
)
tcp_flags
=
dict_search_args
(
rule_conf
,
'tcp'
,
'flags'
)
if
tcp_flags
:
if
dict_search_args
(
rule_conf
,
'protocol'
)
!=
'tcp'
:
raise
ConfigError
(
'Protocol must be tcp when specifying tcp flags'
)
not_flags
=
dict_search_args
(
rule_conf
,
'tcp'
,
'flags'
,
'not'
)
if
not_flags
:
duplicates
=
[
flag
for
flag
in
tcp_flags
if
flag
in
not_flags
]
if
duplicates
:
raise
ConfigError
(
f
'Cannot match a tcp flag as set and not set'
)
for
side
in
[
'destination'
,
'source'
]:
if
side
in
rule_conf
:
side_conf
=
rule_conf
[
side
]
if
'group'
in
side_conf
:
if
len
({
'address_group'
,
'domain_group'
,
'network_group'
}
&
set
(
side_conf
[
'group'
]))
>
1
:
raise
ConfigError
(
'Only one address-group, domain-group or network-group can be specified'
)
for
group
in
valid_groups
:
if
group
in
side_conf
[
'group'
]:
group_name
=
side_conf
[
'group'
][
group
]
if
group_name
.
startswith
(
'!'
):
group_name
=
group_name
[
1
:]
fw_group
=
f
'ipv6_{group}'
if
ipv6
and
group
in
[
'address_group'
,
'network_group'
]
else
group
error_group
=
fw_group
.
replace
(
"_"
,
"-"
)
group_obj
=
dict_search_args
(
policy
[
'firewall_group'
],
fw_group
,
group_name
)
if
group_obj
is
None
:
raise
ConfigError
(
f
'Invalid {error_group} "{group_name}" on policy route rule'
)
if
not
group_obj
:
Warning
(
f
'{error_group} "{group_name}" has no members'
)
if
'port'
in
side_conf
or
dict_search_args
(
side_conf
,
'group'
,
'port_group'
):
if
'protocol'
not
in
rule_conf
:
raise
ConfigError
(
'Protocol must be defined if specifying a port or port-group'
)
if
rule_conf
[
'protocol'
]
not
in
[
'tcp'
,
'udp'
,
'tcp_udp'
]:
raise
ConfigError
(
'Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group'
)
def
verify
(
policy
):
for
route
in
[
'route'
,
'route6'
]:
ipv6
=
route
==
'route6'
if
route
in
policy
:
for
name
,
pol_conf
in
policy
[
route
]
.
items
():
if
'rule'
in
pol_conf
:
for
rule_id
,
rule_conf
in
pol_conf
[
'rule'
]
.
items
():
verify_rule
(
policy
,
name
,
rule_conf
,
ipv6
,
rule_id
)
return
None
def
generate
(
policy
):
if
not
os
.
path
.
exists
(
nftables_conf
):
policy
[
'first_install'
]
=
True
render
(
nftables_conf
,
'firewall/nftables-policy.j2'
,
policy
)
return
None
def
apply_table_marks
(
policy
):
for
route
in
[
'route'
,
'route6'
]:
if
route
in
policy
:
cmd_str
=
'ip'
if
route
==
'route'
else
'ip -6'
tables
=
[]
for
name
,
pol_conf
in
policy
[
route
]
.
items
():
if
'rule'
in
pol_conf
:
for
rule_id
,
rule_conf
in
pol_conf
[
'rule'
]
.
items
():
vrf_table_id
=
None
set_table
=
dict_search_args
(
rule_conf
,
'set'
,
'table'
)
set_vrf
=
dict_search_args
(
rule_conf
,
'set'
,
'vrf'
)
if
set_vrf
:
if
set_vrf
==
'default'
:
vrf_table_id
=
rt_global_vrf
else
:
vrf_table_id
=
get_vrf_tableid
(
set_vrf
)
elif
set_table
:
if
set_table
==
'main'
:
vrf_table_id
=
rt_global_table
else
:
vrf_table_id
=
set_table
if
vrf_table_id
is
not
None
:
vrf_table_id
=
int
(
vrf_table_id
)
if
vrf_table_id
in
tables
:
continue
tables
.
append
(
vrf_table_id
)
table_mark
=
mark_offset
-
vrf_table_id
cmd
(
f
'{cmd_str} rule add pref {vrf_table_id} fwmark {table_mark} table {vrf_table_id}'
)
def
cleanup_table_marks
():
for
cmd_str
in
[
'ip'
,
'ip -6'
]:
json_rules
=
cmd
(
f
'{cmd_str} -j -N rule list'
)
rules
=
loads
(
json_rules
)
for
rule
in
rules
:
if
'fwmark'
not
in
rule
or
'table'
not
in
rule
:
continue
fwmark
=
rule
[
'fwmark'
]
table
=
int
(
rule
[
'table'
])
if
fwmark
[:
2
]
==
'0x'
:
fwmark
=
int
(
fwmark
,
16
)
if
(
int
(
fwmark
)
==
(
mark_offset
-
table
)):
cmd
(
f
'{cmd_str} rule del fwmark {fwmark} table {table}'
)
def
apply
(
policy
):
install_result
=
run
(
f
'nft --file {nftables_conf}'
)
if
install_result
==
1
:
raise
ConfigError
(
'Failed to apply policy based routing'
)
if
'first_install'
not
in
policy
:
cleanup_table_marks
()
apply_table_marks
(
policy
)
if
policy
[
'geoip_sets'
]:
# Call helper script to Update set contents
if
'name'
in
policy
[
'geoip_sets'
]
or
'ipv6_name'
in
policy
[
'geoip_sets'
]:
if
policy
[
'geoip_updated'
]
or
not
geoip_refresh
():
print
(
'Updating GeoIP. Please wait...'
)
geoip_update
(
policy
=
policy
)
return
None
if
__name__
==
'__main__'
:
try
:
c
=
get_config
()
verify
(
c
)
generate
(
c
)
apply
(
c
)
except
ConfigError
as
e
:
print
(
e
)
exit
(
1
)
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Sat, Oct 17, 12:53 AM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4375964
Default Alt Text
policy_route.py (10 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment