summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/https.py9
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py6
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py35
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf8
-rwxr-xr-xsrc/helpers/vyos-load-config.py2
-rwxr-xr-xsrc/migration-scripts/https/4-to-562
-rwxr-xr-xsrc/migration-scripts/interfaces/31-to-324
-rwxr-xr-xsrc/op_mode/bridge.py29
-rwxr-xr-xsrc/op_mode/interfaces.py7
-rwxr-xr-xsrc/services/vyos-http-api-server76
10 files changed, 214 insertions, 24 deletions
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index 010490c7e..26c4343a0 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -122,7 +122,7 @@ def verify(https):
server_block = deepcopy(default_server_block)
data = vhost_dict.get(vhost, {})
server_block['address'] = data.get('listen-address', '*')
- server_block['port'] = data.get('listen-port', '443')
+ server_block['port'] = data.get('port', '443')
server_block_list.append(server_block)
for entry in server_block_list:
@@ -156,7 +156,7 @@ def generate(https):
server_block['id'] = vhost
data = vhost_dict.get(vhost, {})
server_block['address'] = data.get('listen-address', '*')
- server_block['port'] = data.get('listen-port', '443')
+ server_block['port'] = data.get('port', '443')
name = data.get('server-name', ['_'])
server_block['name'] = name
allow_client = data.get('allow-client', {})
@@ -215,14 +215,9 @@ def generate(https):
api_data = vyos.defaults.api_data
api_settings = https.get('api', {})
if api_settings:
- port = api_settings.get('port', '')
- if port:
- api_data['port'] = port
vhosts = https.get('api-restrict', {}).get('virtual-host', [])
if vhosts:
api_data['vhost'] = vhosts[:]
- if 'socket' in list(api_settings):
- api_data['socket'] = True
if api_data:
vhost_list = api_data.get('vhost', [])
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index 0a03a172c..42f084309 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -61,6 +61,12 @@ def get_config(config=None):
# bail out early - no need to further process other nodes
break
+ if 'deleted' not in pppoe:
+ # We always set the MRU value to the MTU size. This code path only re-creates
+ # the old behavior if MRU is not set on the CLI.
+ if 'mru' not in pppoe:
+ pppoe['mru'] = pppoe['mtu']
+
return pppoe
def verify(pppoe):
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 6bf3227d5..4251e611b 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -60,8 +60,14 @@ def get_config(config=None):
vxlan.update({'rebuild_required': {}})
break
+ # When dealing with VNI filtering we need to know what VNI was actually removed,
+ # so build up a dict matching the vlan_to_vni structure but with removed values.
tmp = node_changed(conf, base + [ifname, 'vlan-to-vni'], recursive=True)
- if tmp: vxlan.update({'vlan_to_vni_removed': tmp})
+ if tmp:
+ vxlan.update({'vlan_to_vni_removed': {}})
+ for vlan in tmp:
+ vni = leaf_node_changed(conf, base + [ifname, 'vlan-to-vni', vlan, 'vni'])
+ vxlan['vlan_to_vni_removed'].update({vlan : {'vni' : vni[0]}})
# We need to verify that no other VXLAN tunnel is configured when external
# mode is in use - Linux Kernel limitation
@@ -98,14 +104,31 @@ def verify(vxlan):
if 'vni' not in vxlan and dict_search('parameters.external', vxlan) == None:
raise ConfigError('Must either configure VXLAN "vni" or use "external" CLI option!')
- if dict_search('parameters.external', vxlan):
+ if dict_search('parameters.external', vxlan) != None:
if 'vni' in vxlan:
raise ConfigError('Can not specify both "external" and "VNI"!')
if 'other_tunnels' in vxlan:
- other_tunnels = ', '.join(vxlan['other_tunnels'])
- raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\
- f'CLI option is used. Additional tunnels: {other_tunnels}')
+ # When multiple VXLAN interfaces are defined and "external" is used,
+ # all VXLAN interfaces need to have vni-filter enabled!
+ # See Linux Kernel commit f9c4bb0b245cee35ef66f75bf409c9573d934cf9
+ other_vni_filter = False
+ for tunnel, tunnel_config in vxlan['other_tunnels'].items():
+ if dict_search('parameters.vni_filter', tunnel_config) != None:
+ other_vni_filter = True
+ break
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ vni_filter = True and (dict_search('parameters.vni_filter', vxlan) != None) or False
+ # If either one is enabled, so must be the other. Both can be off and both can be on
+ if (vni_filter and not other_vni_filter) or (not vni_filter and other_vni_filter):
+ raise ConfigError(f'Using multiple VXLAN interfaces with "external" '\
+ 'requires all VXLAN interfaces to have "vni-filter" configured!')
+
+ if not vni_filter and not other_vni_filter:
+ other_tunnels = ', '.join(vxlan['other_tunnels'])
+ raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\
+ f'CLI option is used and "vni-filter" is unset. '\
+ f'Additional tunnels: {other_tunnels}')
if 'gpe' in vxlan and 'external' not in vxlan:
raise ConfigError(f'VXLAN-GPE is only supported when "external" '\
@@ -165,7 +188,7 @@ def verify(vxlan):
raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!')
vnis_used.append(vni)
- if dict_search('parameters.neighbor_suppress', vxlan):
+ if dict_search('parameters.neighbor_suppress', vxlan) != None:
if 'is_bridge_member' not in vxlan:
raise ConfigError('Neighbor suppression requires that VXLAN interface '\
'is member of a bridge interface!')
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index 1c9b8999f..67d96969e 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -105,3 +105,11 @@ net.core.rps_sock_flow_entries = 32768
net.core.default_qdisc=fq_codel
net.ipv4.tcp_congestion_control=bbr
+# VRF - Virtual routing and forwarding
+# When net.vrf.strict_mode=0 (default) it is possible to associate multiple
+# VRF devices to the same table. Conversely, when net.vrf.strict_mode=1 a
+# table can be associated to a single VRF device.
+#
+# A VRF table can be used by the VyOS CLI only once (ensured by verify()),
+# this simply adds an additional Kernel safety net
+net.vrf.strict_mode=1
diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py
index e579e81b2..4ec865454 100755
--- a/src/helpers/vyos-load-config.py
+++ b/src/helpers/vyos-load-config.py
@@ -66,7 +66,7 @@ def get_local_config(filename):
return config_str
-if any(x in file_name for x in protocols):
+if any(file_name.startswith(f'{x}://') for x in protocols):
config_string = vyos.remote.get_remote_config(file_name)
if not config_string:
sys.exit(f"No such config file at '{file_name}'")
diff --git a/src/migration-scripts/https/4-to-5 b/src/migration-scripts/https/4-to-5
new file mode 100755
index 000000000..0dfb6ac19
--- /dev/null
+++ b/src/migration-scripts/https/4-to-5
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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/>.
+
+# T5762: http: api: smoketests fail as they can not establish IPv6 connection
+# to uvicorn backend server, always make the UNIX domain socket the
+# default way of communication
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['service', 'https']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+# Delete "socket" CLI option - we always use UNIX domain sockets for
+# NGINX <-> API server communication
+if config.exists(base + ['api', 'socket']):
+ config.delete(base + ['api', 'socket'])
+
+# There is no need for an API service port, as UNIX domain sockets
+# are used
+if config.exists(base + ['api', 'port']):
+ config.delete(base + ['api', 'port'])
+
+# rename listen-port -> port ver virtual-host
+if config.exists(base + ['virtual-host']):
+ for vhost in config.list_nodes(base + ['virtual-host']):
+ if config.exists(base + ['virtual-host', vhost, 'listen-port']):
+ config.rename(base + ['virtual-host', vhost, 'listen-port'], 'port')
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/31-to-32 b/src/migration-scripts/interfaces/31-to-32
index ca3d19320..0fc27b70a 100755
--- a/src/migration-scripts/interfaces/31-to-32
+++ b/src/migration-scripts/interfaces/31-to-32
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# T5671: change port to IANA assigned default port
+# T5759: change default MTU 1450 -> 1500
from sys import argv
from sys import exit
@@ -43,6 +44,9 @@ for vxlan in config.list_nodes(base):
if not config.exists(base + [vxlan, 'port']):
config.set(base + [vxlan, 'port'], value='8472')
+ if not config.exists(base + [vxlan, 'mtu']):
+ config.set(base + [vxlan, 'mtu'], value='1450')
+
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py
index 185db4f20..412a4eba8 100755
--- a/src/op_mode/bridge.py
+++ b/src/op_mode/bridge.py
@@ -56,6 +56,13 @@ def _get_raw_data_vlan(tunnel:bool=False):
data_dict = json.loads(json_data)
return data_dict
+def _get_raw_data_vni() -> dict:
+ """
+ :returns dict
+ """
+ json_data = cmd(f'bridge --json vni show')
+ data_dict = json.loads(json_data)
+ return data_dict
def _get_raw_data_fdb(bridge):
"""Get MAC-address for the bridge brX
@@ -165,6 +172,22 @@ def _get_formatted_output_vlan_tunnel(data):
output = tabulate(data_entries, headers)
return output
+def _get_formatted_output_vni(data):
+ data_entries = []
+ for entry in data:
+ interface = entry.get('ifname')
+ vlans = entry.get('vnis')
+ for vlan_entry in vlans:
+ vlan = vlan_entry.get('vni')
+ if vlan_entry.get('vniEnd'):
+ vlan_end = vlan_entry.get('vniEnd')
+ vlan = f'{vlan}-{vlan_end}'
+ data_entries.append([interface, vlan])
+
+ headers = ["Interface", "VNI"]
+ output = tabulate(data_entries, headers)
+ return output
+
def _get_formatted_output_fdb(data):
data_entries = []
for entry in data:
@@ -228,6 +251,12 @@ def show_vlan(raw: bool, tunnel: typing.Optional[bool]):
else:
return _get_formatted_output_vlan(bridge_vlan)
+def show_vni(raw: bool):
+ bridge_vni = _get_raw_data_vni()
+ if raw:
+ return bridge_vni
+ else:
+ return _get_formatted_output_vni(bridge_vni)
def show_fdb(raw: bool, interface: str):
fdb_data = _get_raw_data_fdb(interface)
diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py
index c626535b5..14ffdca9f 100755
--- a/src/op_mode/interfaces.py
+++ b/src/op_mode/interfaces.py
@@ -235,6 +235,11 @@ def _get_summary_data(ifname: typing.Optional[str],
if iftype is None:
iftype = ''
ret = []
+
+ def is_interface_has_mac(interface_name):
+ interface_no_mac = ('tun', 'wg')
+ return not any(interface_name.startswith(prefix) for prefix in interface_no_mac)
+
for interface in filtered_interfaces(ifname, iftype, vif, vrrp):
res_intf = {}
@@ -244,7 +249,7 @@ def _get_summary_data(ifname: typing.Optional[str],
res_intf['addr'] = [_ for _ in interface.get_addr() if not _.startswith('fe80::')]
res_intf['description'] = interface.get_alias()
res_intf['mtu'] = interface.get_mtu()
- res_intf['mac'] = interface.get_mac()
+ res_intf['mac'] = interface.get_mac() if is_interface_has_mac(interface.ifname) else 'n/a'
res_intf['vrf'] = interface.get_vrf()
ret.append(res_intf)
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 3a9efb73e..85d7884b6 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -223,6 +223,19 @@ class ShowModel(ApiModel):
}
}
+class RebootModel(ApiModel):
+ op: StrictStr
+ path: List[StrictStr]
+
+ class Config:
+ schema_extra = {
+ "example": {
+ "key": "id_key",
+ "op": "reboot",
+ "path": ["op", "mode", "path"],
+ }
+ }
+
class ResetModel(ApiModel):
op: StrictStr
path: List[StrictStr]
@@ -236,6 +249,19 @@ class ResetModel(ApiModel):
}
}
+class PoweroffModel(ApiModel):
+ op: StrictStr
+ path: List[StrictStr]
+
+ class Config:
+ schema_extra = {
+ "example": {
+ "key": "id_key",
+ "op": "poweroff",
+ "path": ["op", "mode", "path"],
+ }
+ }
+
class Success(BaseModel):
success: bool
@@ -713,6 +739,26 @@ def show_op(data: ShowModel):
return success(res)
+@app.post('/reboot')
+def reboot_op(data: RebootModel):
+ session = app.state.vyos_session
+
+ op = data.op
+ path = data.path
+
+ try:
+ if op == 'reboot':
+ res = session.reboot(path)
+ else:
+ return error(400, f"'{op}' is not a valid operation")
+ except ConfigSessionError as e:
+ return error(400, str(e))
+ except Exception as e:
+ logger.critical(traceback.format_exc())
+ return error(500, "An internal error occured. Check the logs for details.")
+
+ return success(res)
+
@app.post('/reset')
def reset_op(data: ResetModel):
session = app.state.vyos_session
@@ -733,6 +779,26 @@ def reset_op(data: ResetModel):
return success(res)
+@app.post('/poweroff')
+def poweroff_op(data: PoweroffModel):
+ session = app.state.vyos_session
+
+ op = data.op
+ path = data.path
+
+ try:
+ if op == 'poweroff':
+ res = session.poweroff(path)
+ else:
+ return error(400, f"'{op}' is not a valid operation")
+ except ConfigSessionError as e:
+ return error(400, str(e))
+ except Exception as e:
+ logger.critical(traceback.format_exc())
+ return error(500, "An internal error occured. Check the logs for details.")
+
+ return success(res)
+
###
# GraphQL integration
@@ -825,15 +891,7 @@ def initialization(session: ConfigSession, app: FastAPI = app):
if app.state.vyos_graphql:
graphql_init(app)
- if not server_config['socket']:
- config = ApiServerConfig(app,
- host=server_config["listen_address"],
- port=int(server_config["port"]),
- proxy_headers=True)
- else:
- config = ApiServerConfig(app,
- uds="/run/api.sock",
- proxy_headers=True)
+ config = ApiServerConfig(app, uds="/run/api.sock", proxy_headers=True)
server = ApiServer(config)
def run_server():