summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py20
-rwxr-xr-xsrc/etc/opennhrp/opennhrp-script.py15
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/show_interfaces_input_filter.py16
-rwxr-xr-xsrc/op_mode/nat.py101
-rw-r--r--src/services/api/graphql/bindings.py3
-rw-r--r--src/services/api/graphql/graphql/errors.py8
-rw-r--r--src/services/api/graphql/graphql/mutations.py13
-rw-r--r--src/services/api/graphql/graphql/queries.py13
-rw-r--r--src/services/api/graphql/session/errors/op_mode_errors.py13
-rw-r--r--src/services/api/graphql/session/session.py13
-rwxr-xr-xsrc/services/api/graphql/utils/schema_from_op_mode.py43
11 files changed, 212 insertions, 46 deletions
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 18b32edab..53df006a4 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -40,23 +40,6 @@ custom_scripts_dir = '/etc/telegraf/custom_scripts'
syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf'
systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf'
-def get_interfaces(type='', vlan=True):
- """
- get_interfaces()
- ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
-
- get_interfaces("dummy")
- ['dum0']
- """
- interfaces = []
- ifaces = Section.interfaces(type)
- for iface in ifaces:
- if vlan == False and '.' in iface:
- continue
- interfaces.append(iface)
-
- return interfaces
-
def get_nft_filter_chains():
""" Get nft chains for table filter """
nft = cmd('nft --json list table ip filter')
@@ -70,7 +53,6 @@ def get_nft_filter_chains():
return chain_list
-
def get_config(config=None):
if config:
conf = config
@@ -93,7 +75,7 @@ def get_config(config=None):
monitoring = dict_merge(default_values, monitoring)
monitoring['custom_scripts_dir'] = custom_scripts_dir
- monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False)
+ monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False)
monitoring['nft_chains'] = get_nft_filter_chains()
# Redefine azure group-metrics 'single-table' and 'table-per-metric'
diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py
index a5293c97e..bf25a7331 100755
--- a/src/etc/opennhrp/opennhrp-script.py
+++ b/src/etc/opennhrp/opennhrp-script.py
@@ -81,7 +81,13 @@ def vici_ike_terminate(list_ikeid: list[str]) -> bool:
session = vici.Session()
for ikeid in list_ikeid:
logger.info(f'Terminating IKE SA with id {ikeid}')
- session.terminate({'ike-id': ikeid, 'timeout': '-1'})
+ session_generator = session.terminate(
+ {'ike-id': ikeid, 'timeout': '-1'})
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
return True
except Exception as err:
logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}')
@@ -175,13 +181,18 @@ def vici_initiate(conn: str, child_sa: str, src_addr: str,
f'src_addr: {src_addr}, dst_addr: {dest_addr}')
try:
session = vici.Session()
- session.initiate({
+ session_generator = session.initiate({
'ike': conn,
'child': child_sa,
'timeout': '-1',
'my-host': src_addr,
'other-host': dest_addr
})
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
return True
except Exception as err:
logger.error(f'Unable to initiate connection {err}')
diff --git a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
index 0c7474156..6f14d6a8e 100755
--- a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
+++ b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
@@ -5,20 +5,6 @@ from vyos.ifconfig import Interface
import time
-def get_interfaces(type='', vlan=True):
- """
- Get interfaces:
- ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
- """
- interfaces = []
- ifaces = Section.interfaces(type)
- for iface in ifaces:
- if vlan == False and '.' in iface:
- continue
- interfaces.append(iface)
-
- return interfaces
-
def get_interface_addresses(iface, link_local_v6=False):
"""
Get IP and IPv6 addresses from interface in one string
@@ -77,7 +63,7 @@ def get_interface_oper_state(iface):
return oper_state
-interfaces = get_interfaces()
+interfaces = Section.interfaces('')
for iface in interfaces:
print(f'show_interfaces,interface={iface} '
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index dec04aa48..1339d5b92 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -17,6 +17,7 @@
import jmespath
import json
import sys
+import xmltodict
from sys import exit
from tabulate import tabulate
@@ -27,6 +28,29 @@ from vyos.util import dict_search
import vyos.opmode
+def _get_xml_translation(direction, family):
+ """
+ Get conntrack XML output --src-nat|--dst-nat
+ """
+ if direction == 'source':
+ opt = '--src-nat'
+ if direction == 'destination':
+ opt = '--dst-nat'
+ return cmd(f'sudo conntrack --dump --family {family} {opt} --output xml')
+
+
+def _xml_to_dict(xml):
+ """
+ Convert XML to dictionary
+ Return: dictionary
+ """
+ parse = xmltodict.parse(xml, attr_prefix='')
+ # If only one conntrack entry we must change dict
+ if 'meta' in parse['conntrack']['flow']:
+ return dict(conntrack={'flow': [parse['conntrack']['flow']]})
+ return parse
+
+
def _get_json_data(direction, family):
"""
Get NAT format JSON
@@ -52,6 +76,22 @@ def _get_raw_data_rules(direction, family):
return rules
+def _get_raw_translation(direction, family):
+ """
+ Return: dictionary
+ """
+ xml = _get_xml_translation(direction, family)
+ if len(xml) == 0:
+ output = {'conntrack':
+ {
+ 'error': True,
+ 'reason': 'entries not found'
+ }
+ }
+ return output
+ return _xml_to_dict(xml)
+
+
def _get_formatted_output_rules(data, direction, family):
# Add default values before loop
sport, dport, proto = 'any', 'any', 'any'
@@ -180,6 +220,58 @@ def _get_formatted_output_statistics(data, direction):
return output
+def _get_formatted_translation(dict_data, nat_direction, family):
+ data_entries = []
+ if 'error' in dict_data['conntrack']:
+ return 'Entries not found'
+ for entry in dict_data['conntrack']['flow']:
+ orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {}
+ reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {}
+ proto = {}
+ for meta in entry['meta']:
+ direction = meta['direction']
+ if direction in ['original']:
+ if 'layer3' in meta:
+ orig_src = meta['layer3']['src']
+ orig_dst = meta['layer3']['dst']
+ if 'layer4' in meta:
+ if meta.get('layer4').get('sport'):
+ orig_sport = meta['layer4']['sport']
+ if meta.get('layer4').get('dport'):
+ orig_dport = meta['layer4']['dport']
+ proto = meta['layer4']['protoname']
+ if direction in ['reply']:
+ if 'layer3' in meta:
+ reply_src = meta['layer3']['src']
+ reply_dst = meta['layer3']['dst']
+ if 'layer4' in meta:
+ if meta.get('layer4').get('sport'):
+ reply_sport = meta['layer4']['sport']
+ if meta.get('layer4').get('dport'):
+ reply_dport = meta['layer4']['dport']
+ proto = meta['layer4']['protoname']
+ if direction == 'independent':
+ conn_id = meta['id']
+ timeout = meta['timeout']
+ orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src
+ orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst
+ reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src
+ reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst
+ state = meta['state'] if 'state' in meta else ''
+ mark = meta['mark']
+ zone = meta['zone'] if 'zone' in meta else ''
+ if nat_direction == 'source':
+ data_entries.append(
+ [orig_src, reply_dst, proto, timeout, mark, zone])
+ elif nat_direction == 'destination':
+ data_entries.append(
+ [orig_dst, reply_src, proto, timeout, mark, zone])
+
+ headers = ["Pre-NAT", "Post-NAT", "Proto", "Timeout", "Mark", "Zone"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return output
+
+
def show_rules(raw: bool, direction: str, family: str):
nat_rules = _get_raw_data_rules(direction, family)
if raw:
@@ -196,6 +288,15 @@ def show_statistics(raw: bool, direction: str, family: str):
return _get_formatted_output_statistics(nat_statistics, direction)
+def show_translations(raw: bool, direction: str, family: str):
+ family = 'ipv6' if family == 'inet6' else 'ipv4'
+ nat_translation = _get_raw_translation(direction, family)
+ if raw:
+ return nat_translation
+ else:
+ return _get_formatted_translation(nat_translation, direction, family)
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py
index 049d59de7..0b1260912 100644
--- a/src/services/api/graphql/bindings.py
+++ b/src/services/api/graphql/bindings.py
@@ -17,6 +17,7 @@ import vyos.defaults
from . graphql.queries import query
from . graphql.mutations import mutation
from . graphql.directives import directives_dict
+from . graphql.errors import op_mode_error
from . utils.schema_from_op_mode import generate_op_mode_definitions
from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers
@@ -27,6 +28,6 @@ def generate_schema():
type_defs = load_schema_from_path(api_schema_dir)
- schema = make_executable_schema(type_defs, query, mutation, snake_case_fallback_resolvers, directives=directives_dict)
+ schema = make_executable_schema(type_defs, query, op_mode_error, mutation, snake_case_fallback_resolvers, directives=directives_dict)
return schema
diff --git a/src/services/api/graphql/graphql/errors.py b/src/services/api/graphql/graphql/errors.py
new file mode 100644
index 000000000..1066300e0
--- /dev/null
+++ b/src/services/api/graphql/graphql/errors.py
@@ -0,0 +1,8 @@
+
+from ariadne import InterfaceType
+
+op_mode_error = InterfaceType("OpModeError")
+
+@op_mode_error.type_resolver
+def resolve_op_mode_error(obj, *_):
+ return obj['name']
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index c8ae0f516..1b77cff87 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -22,6 +22,8 @@ from makefun import with_signature
from .. import state
from .. import key_auth
from api.graphql.session.session import Session
+from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code
+from vyos.opmode import Error as OpModeError
mutation = ObjectType("Mutation")
@@ -86,10 +88,19 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
"success": True,
"data": data
}
+ except OpModeError as e:
+ typename = type(e).__name__
+ return {
+ "success": False,
+ "errore": ['op_mode_error'],
+ "op_mode_error": {"name": f"{typename}",
+ "message": op_mode_err_msg.get(typename, "Unknown"),
+ "vyos_code": op_mode_err_code.get(typename, 9999)}
+ }
except Exception as error:
return {
"success": False,
- "errors": [str(error)]
+ "errors": [repr(error)]
}
return func_impl
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index 921a66274..8ae61b704 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -22,6 +22,8 @@ from makefun import with_signature
from .. import state
from .. import key_auth
from api.graphql.session.session import Session
+from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code
+from vyos.opmode import Error as OpModeError
query = ObjectType("Query")
@@ -86,10 +88,19 @@ def make_query_resolver(query_name, class_name, session_func):
"success": True,
"data": data
}
+ except OpModeError as e:
+ typename = type(e).__name__
+ return {
+ "success": False,
+ "errors": ['op_mode_error'],
+ "op_mode_error": {"name": f"{typename}",
+ "message": op_mode_err_msg.get(typename, "Unknown"),
+ "vyos_code": op_mode_err_code.get(typename, 9999)}
+ }
except Exception as error:
return {
"success": False,
- "errors": [str(error)]
+ "errors": [repr(error)]
}
return func_impl
diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py
new file mode 100644
index 000000000..7ba75455d
--- /dev/null
+++ b/src/services/api/graphql/session/errors/op_mode_errors.py
@@ -0,0 +1,13 @@
+
+
+op_mode_err_msg = {
+ "UnconfiguredSubsystem": "subsystem is not configured or not running",
+ "DataUnavailable": "data currently unavailable",
+ "PermissionDenied": "client does not have permission"
+}
+
+op_mode_err_code = {
+ "UnconfiguredSubsystem": 2000,
+ "DataUnavailable": 2001,
+ "PermissionDenied": 1003
+}
diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py
index 23bc7154c..93e1c328e 100644
--- a/src/services/api/graphql/session/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -22,6 +22,7 @@ from vyos.config import Config
from vyos.configtree import ConfigTree
from vyos.defaults import directories
from vyos.template import render
+from vyos.opmode import Error as OpModeError
from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name
@@ -177,10 +178,10 @@ class Session:
mod = load_op_mode_as_module(f'{scriptname}')
func = getattr(mod, func_name)
- if len(list(data)) > 0:
+ try:
res = func(True, **data)
- else:
- res = func(True)
+ except OpModeError as e:
+ raise e
return res
@@ -199,9 +200,9 @@ class Session:
mod = load_op_mode_as_module(f'{scriptname}')
func = getattr(mod, func_name)
- if len(list(data)) > 0:
+ try:
res = func(**data)
- else:
- res = func()
+ except OpModeError as e:
+ raise e
return res
diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py
index f990aae52..379d15250 100755
--- a/src/services/api/graphql/utils/schema_from_op_mode.py
+++ b/src/services/api/graphql/utils/schema_from_op_mode.py
@@ -21,7 +21,7 @@
import os
import json
import typing
-from inspect import signature, getmembers, isfunction
+from inspect import signature, getmembers, isfunction, isclass, getmro
from jinja2 import Template
from vyos.defaults import directories
@@ -35,6 +35,7 @@ SCHEMA_PATH = directories['api_schema']
DATA_DIR = directories['data']
op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json')
+op_mode_error_schema = 'op_mode_error.graphql'
schema_data: dict = {'schema_name': '',
'schema_fields': []}
@@ -53,6 +54,7 @@ type {{ schema_name }} {
type {{ schema_name }}Result {
data: {{ schema_name }}
+ op_mode_error: OpModeError
success: Boolean!
errors: [String]
}
@@ -76,6 +78,7 @@ type {{ schema_name }} {
type {{ schema_name }}Result {
data: {{ schema_name }}
+ op_mode_error: OpModeError
success: Boolean!
errors: [String]
}
@@ -85,6 +88,21 @@ extend type Mutation {
}
"""
+error_template = """
+interface OpModeError {
+ name: String!
+ message: String!
+ vyos_code: Int!
+}
+{% for name in error_names %}
+type {{ name }} implements OpModeError {
+ name: String!
+ message: String!
+ vyos_code: Int!
+}
+{%- endfor %}
+"""
+
def _snake_to_pascal_case(name: str) -> str:
res = ''.join(map(str.title, name.split('_')))
return res
@@ -136,7 +154,30 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str:
return res
+def create_error_schema():
+ from vyos import opmode
+
+ e = Exception
+ err_types = getmembers(opmode, isclass)
+ err_types = [k for k in err_types if issubclass(k[1], e)]
+ # drop base class, to be replaced by interface type. Find the class
+ # programmatically, in case the base class name changes.
+ for i in range(len(err_types)):
+ if err_types[i][1] in getmro(err_types[i-1][1]):
+ del err_types[i]
+ break
+ err_names = [k[0] for k in err_types]
+ error_data = {'error_names': err_names}
+ j2_template = Template(error_template)
+ res = j2_template.render(error_data)
+
+ return res
+
def generate_op_mode_definitions():
+ out = create_error_schema()
+ with open(f'{SCHEMA_PATH}/{op_mode_error_schema}', 'w') as f:
+ f.write(out)
+
with open(op_mode_include_file) as f:
op_mode_files = json.load(f)