summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Estabrook <jestabro@vyos.io>2026-08-24 10:40:13 -0500
committerGitHub <noreply@github.com>2026-08-24 10:40:13 -0500
commit745a4afd9bab8a99fdfdb38febe7bf39ac8e5a25 (patch)
tree6d39c115ec2403d3dd45417704adee43d5a22d1f
parent0712fac9dddb4dbfede4409867d2973ccbfb5aa2 (diff)
parentea65e1d24bf9327edd2244a81e9c307f3001b16f (diff)
downloadvyos-1x-745a4afd9bab8a99fdfdb38febe7bf39ac8e5a25.tar.gz
vyos-1x-745a4afd9bab8a99fdfdb38febe7bf39ac8e5a25.zip
Merge pull request #5415 from jvoss/api_ping
http-api: T9224: add ping endpoint to REST API
-rw-r--r--data/templates/https/nginx.default.j22
-rw-r--r--python/vyos/configsession.py8
-rw-r--r--src/services/api/rest/models.py25
-rw-r--r--src/services/api/rest/routers.py26
4 files changed, 60 insertions, 1 deletions
diff --git a/data/templates/https/nginx.default.j2 b/data/templates/https/nginx.default.j2
index cf87fb309..e0ebe79fc 100644
--- a/data/templates/https/nginx.default.j2
+++ b/data/templates/https/nginx.default.j2
@@ -49,7 +49,7 @@ server {
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
# proxy settings for HTTP API, if enabled; 503, if not
- location ~ ^/(retrieve|configure|config-file|image|import-pki|container-image|generate|show|reboot|reset|poweroff|traceroute|info|docs|openapi.json|redoc|graphql|renew) {
+ location ~ ^/(retrieve|configure|config-file|image|import-pki|container-image|generate|show|reboot|reset|poweroff|ping|traceroute|info|docs|openapi.json|redoc|graphql|renew) {
{% if api is vyos_defined %}
proxy_pass http://unix:/run/api.sock;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 609ad79b8..3f1f4f31f 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -88,6 +88,7 @@ RENEW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'renew']
POWEROFF = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'poweroff']
OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add']
OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete']
+PING = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'ping']
TRACEROUTE = [
'/usr/libexec/vyos/op_mode/mtr_execute.py',
'mtr',
@@ -486,6 +487,13 @@ class ConfigSession(object):
out = self.__run_command(SHOW + ['container', 'image'])
return out
+ def ping(self, host, count: int = 5, vrf: str | None = None):
+ cmd = PING + [host, 'count', str(count)]
+ if vrf is not None:
+ cmd += ['vrf', vrf]
+ out = self.__run_command(cmd)
+ return out
+
def traceroute(self, host):
out = self.__run_command(TRACEROUTE + [host])
return out
diff --git a/src/services/api/rest/models.py b/src/services/api/rest/models.py
index bfea17344..d3ee3a08e 100644
--- a/src/services/api/rest/models.py
+++ b/src/services/api/rest/models.py
@@ -304,6 +304,31 @@ class PoweroffModel(ApiModel):
}
+class PingModel(ApiModel):
+ op: StrictStr
+ host: StrictStr
+ count: StrictInt = 5
+ vrf: StrictStr = None
+
+ @field_validator('count')
+ @classmethod
+ def check_between(cls, count: int) -> int:
+ if not 1 <= count <= 10:
+ raise ValueError('count must be between 1 and 10')
+ return count
+
+ class Config:
+ schema_extra = {
+ 'example': {
+ 'key': 'id_key',
+ 'op': 'ping',
+ 'host': 'host',
+ 'count': 5,
+ 'vrf': 'vrf',
+ }
+ }
+
+
class TracerouteModel(ApiModel):
op: StrictStr
host: StrictStr
diff --git a/src/services/api/rest/routers.py b/src/services/api/rest/routers.py
index fe67d4612..a241cabf8 100644
--- a/src/services/api/rest/routers.py
+++ b/src/services/api/rest/routers.py
@@ -74,6 +74,7 @@ from .models import RebootModel
from .models import ResetModel
from .models import RenewModel
from .models import ImportPkiModel
+from .models import PingModel
from .models import PoweroffModel
from .models import TracerouteModel
@@ -218,6 +219,7 @@ class MultipartRequest(Request):
'/container-image',
'/image',
'/configure-section',
+ '/ping',
'/traceroute',
):
if 'path' not in c:
@@ -959,6 +961,30 @@ def poweroff_op(data: PoweroffModel):
return success(res)
+@router.post('/ping')
+def ping_op(data: PingModel):
+ state = SessionState()
+ session = state.session
+
+ op = data.op
+ host = data.host
+ count = data.count
+ vrf = data.vrf
+
+ try:
+ if op == 'ping':
+ res = session.ping(host, count, vrf)
+ else:
+ return error(400, f"'{op}' is not a valid operation")
+ except ConfigSessionError as e:
+ return error(400, str(e))
+ except Exception:
+ LOG.critical(traceback.format_exc())
+ return error(500, 'An internal error occurred. Check the logs for details.')
+
+ return success(res)
+
+
@router.post('/traceroute')
def traceroute_op(data: TracerouteModel):
state = SessionState()