mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-06-14 17:46:05 +00:00
Merge pull request #1789 from Orange-OpenSource/master
ospfd: Add json output for Segment Routing
This commit is contained in:
commit
cc7d9cab99
@ -5,6 +5,26 @@ This is an EXPERIMENTAL support of draft
|
|||||||
`draft-ietf-ospf-segment-routing-extensions-24`.
|
`draft-ietf-ospf-segment-routing-extensions-24`.
|
||||||
DON'T use it for production network.
|
DON'T use it for production network.
|
||||||
|
|
||||||
|
Supported Features
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Automatic computation of Primary and Backup Adjacency SID with
|
||||||
|
Cisco experimental remote IP address
|
||||||
|
* SRGB configuration
|
||||||
|
* Prefix configuration for Node SID with optional NO-PHP flag (Linux
|
||||||
|
kernel support both mode)
|
||||||
|
* Node MSD configuration (with Linux Kernel >= 4.10 a maximum of 32 labels
|
||||||
|
could be stack)
|
||||||
|
* Automatic provisioning of MPLS table
|
||||||
|
* Static route configuration with label stack up to 32 labels
|
||||||
|
|
||||||
|
Interoperability
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* tested on various topology including point-to-point and LAN interfaces
|
||||||
|
in a mix of Free Range Routing instance and Cisco IOS-XR 6.0.x
|
||||||
|
* check OSPF LSA conformity with latest wireshark release 2.5.0-rc
|
||||||
|
|
||||||
Implementation details
|
Implementation details
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@ -248,9 +268,6 @@ Known limitations
|
|||||||
* MPLS table are not flush at startup. Thus, restarting zebra process is
|
* MPLS table are not flush at startup. Thus, restarting zebra process is
|
||||||
mandatory to remove old MPLS entries in the data plane after a crash of
|
mandatory to remove old MPLS entries in the data plane after a crash of
|
||||||
ospfd daemon
|
ospfd daemon
|
||||||
* Due to a bug in OSPF Opaque, LSA are not flood when enable Segment Routing
|
|
||||||
through CLI once OSPFd started. You must configure Segment Routing within
|
|
||||||
configuration file before launching OSPFd
|
|
||||||
* With NO Penultimate Hop Popping, it is not possible to express a Segment
|
* With NO Penultimate Hop Popping, it is not possible to express a Segment
|
||||||
Path with an Adjacency SID due to the impossibility for the Linux Kernel to
|
Path with an Adjacency SID due to the impossibility for the Linux Kernel to
|
||||||
perform double POP instruction.
|
perform double POP instruction.
|
||||||
|
@ -759,10 +759,11 @@ currently supported. The 'no-php-flag' means NO Penultimate Hop Popping that
|
|||||||
allows SR node to request to its neighbor to not pop the label.
|
allows SR node to request to its neighbor to not pop the label.
|
||||||
@end deffn
|
@end deffn
|
||||||
|
|
||||||
@deffn {Command} {show ip ospf database segment-routing} {}
|
@deffn {Command} {show ip ospf database segment-routing [json]} {}
|
||||||
@deffnx {Command} {show ip ospf database segment-routing adv-router @var{adv-router}} {}
|
@deffnx {Command} {show ip ospf database segment-routing adv-router @var{adv-router} [json]} {}
|
||||||
@deffnx {Command} {show ip ospf database segment-routing self-originate} {}
|
@deffnx {Command} {show ip ospf database segment-routing self-originate [json]} {}
|
||||||
Show Segment Routing Data Base, all SR nodes, specific advertized router or self router.
|
Show Segment Routing Data Base, all SR nodes, specific advertized router or self router.
|
||||||
|
Optional Json output could be obtain by adding 'json' at the end of the command.
|
||||||
@end deffn
|
@end deffn
|
||||||
|
|
||||||
@node Debugging OSPF
|
@node Debugging OSPF
|
||||||
|
186
ospfd/ospf_sr.c
186
ospfd/ospf_sr.c
@ -47,6 +47,7 @@
|
|||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "vty.h"
|
#include "vty.h"
|
||||||
#include "zclient.h"
|
#include "zclient.h"
|
||||||
|
#include <lib/json.h>
|
||||||
|
|
||||||
#include "ospfd/ospfd.h"
|
#include "ospfd/ospfd.h"
|
||||||
#include "ospfd/ospf_interface.h"
|
#include "ospfd/ospf_interface.h"
|
||||||
@ -2130,91 +2131,197 @@ DEFUN (no_sr_prefix_sid,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void show_vty_sr_node(struct vty *vty, struct sr_node *srn)
|
static void show_sr_node(struct vty *vty, struct json_object *json,
|
||||||
|
struct sr_node *srn)
|
||||||
{
|
{
|
||||||
|
|
||||||
struct listnode *node;
|
struct listnode *node;
|
||||||
struct sr_link *srl;
|
struct sr_link *srl;
|
||||||
struct sr_prefix *srp;
|
struct sr_prefix *srp;
|
||||||
struct interface *itf;
|
struct interface *itf;
|
||||||
char pref[16];
|
char pref[19];
|
||||||
char sid[22];
|
char sid[22];
|
||||||
char label[8];
|
char label[8];
|
||||||
|
json_object *json_node = NULL, *json_algo, *json_obj;
|
||||||
|
json_object *json_prefix = NULL, *json_link = NULL;
|
||||||
|
|
||||||
/* Sanity Check */
|
/* Sanity Check */
|
||||||
if (srn == NULL)
|
if (srn == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
vty_out(vty, "SR-Node: %s", inet_ntoa(srn->adv_router));
|
if (json) {
|
||||||
vty_out(vty, "\tSRGB (Size/Label): %u/%u", srn->srgb.range_size,
|
json_node = json_object_new_object();
|
||||||
|
json_object_string_add(json_node, "routerID",
|
||||||
|
inet_ntoa(srn->adv_router));
|
||||||
|
json_object_int_add(json_node, "srgbSize",
|
||||||
|
srn->srgb.range_size);
|
||||||
|
json_object_int_add(json_node, "srgbLabel",
|
||||||
srn->srgb.lower_bound);
|
srn->srgb.lower_bound);
|
||||||
|
json_algo = json_object_new_array();
|
||||||
|
json_object_object_add(json_node, "algorithms", json_algo);
|
||||||
|
for (int i = 0; i < ALGORITHM_COUNT; i++) {
|
||||||
|
if (srn->algo[i] == SR_ALGORITHM_UNSET)
|
||||||
|
continue;
|
||||||
|
json_obj = json_object_new_object();
|
||||||
|
char tmp[2];
|
||||||
|
|
||||||
|
snprintf(tmp, 2, "%u", i);
|
||||||
|
json_object_string_add(json_obj, tmp,
|
||||||
|
srn->algo[i] == SR_ALGORITHM_SPF ?
|
||||||
|
"SPF" : "S-SPF");
|
||||||
|
json_object_array_add(json_algo, json_obj);
|
||||||
|
}
|
||||||
|
if (srn->msd != 0)
|
||||||
|
json_object_int_add(json_node, "nodeMsd", srn->msd);
|
||||||
|
} else {
|
||||||
|
vty_out(vty, "SR-Node: %s", inet_ntoa(srn->adv_router));
|
||||||
|
vty_out(vty, "\tSRGB (Size/Label): %u/%u",
|
||||||
|
srn->srgb.range_size, srn->srgb.lower_bound);
|
||||||
vty_out(vty, "\tAlgorithm(s): %s",
|
vty_out(vty, "\tAlgorithm(s): %s",
|
||||||
srn->algo[0] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF");
|
srn->algo[0] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF");
|
||||||
for (int i = 1; i < ALGORITHM_COUNT; i++) {
|
for (int i = 1; i < ALGORITHM_COUNT; i++) {
|
||||||
if (srn->algo[i] == SR_ALGORITHM_UNSET)
|
if (srn->algo[i] == SR_ALGORITHM_UNSET)
|
||||||
continue;
|
continue;
|
||||||
vty_out(vty, "/%s",
|
vty_out(vty, "/%s",
|
||||||
srn->algo[i] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF");
|
srn->algo[i] == SR_ALGORITHM_SPF ?
|
||||||
|
"SPF" : "S-SPF");
|
||||||
}
|
}
|
||||||
if (srn->msd != 0)
|
if (srn->msd != 0)
|
||||||
vty_out(vty, "\tMSD: %u", srn->msd);
|
vty_out(vty, "\tMSD: %u", srn->msd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json) {
|
||||||
vty_out(vty,
|
vty_out(vty,
|
||||||
"\n\n Prefix or Link Label In Label Out "
|
"\n\n Prefix or Link Label In Label Out "
|
||||||
"Node or Adj. SID Interface Nexthop\n");
|
"Node or Adj. SID Interface Nexthop\n");
|
||||||
vty_out(vty,
|
vty_out(vty,
|
||||||
"------------------ -------- --------- "
|
"------------------ -------- --------- "
|
||||||
"--------------------- --------- ---------------\n");
|
"--------------------- --------- ---------------\n");
|
||||||
|
}
|
||||||
for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) {
|
for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) {
|
||||||
strncpy(pref, inet_ntoa(srp->nhlfe.prefv4.prefix), 16);
|
snprintf(pref, 19, "%s/%u",
|
||||||
|
inet_ntoa(srp->nhlfe.prefv4.prefix),
|
||||||
|
srp->nhlfe.prefv4.prefixlen);
|
||||||
snprintf(sid, 22, "SR Pfx (idx %u)", srp->sid);
|
snprintf(sid, 22, "SR Pfx (idx %u)", srp->sid);
|
||||||
if (srp->nhlfe.label_out == MPLS_LABEL_IMPLICIT_NULL)
|
if (srp->nhlfe.label_out == MPLS_LABEL_IMPLICIT_NULL)
|
||||||
sprintf(label, "pop");
|
sprintf(label, "pop");
|
||||||
else
|
else
|
||||||
sprintf(label, "%u", srp->nhlfe.label_out);
|
sprintf(label, "%u", srp->nhlfe.label_out);
|
||||||
itf = if_lookup_by_index(srp->nhlfe.ifindex, VRF_DEFAULT);
|
itf = if_lookup_by_index(srp->nhlfe.ifindex, VRF_DEFAULT);
|
||||||
vty_out(vty, "%15s/%u %8u %9s %21s %9s %15s\n", pref,
|
if (json) {
|
||||||
srp->nhlfe.prefv4.prefixlen, srp->nhlfe.label_in, label,
|
if (!json_prefix) {
|
||||||
|
json_prefix = json_object_new_array();
|
||||||
|
json_object_object_add(json_node,
|
||||||
|
"extendedPrefix", json_prefix);
|
||||||
|
}
|
||||||
|
json_obj = json_object_new_object();
|
||||||
|
json_object_string_add(json_obj, "prefix", pref);
|
||||||
|
json_object_int_add(json_obj, "sid", srp->sid);
|
||||||
|
json_object_int_add(json_obj, "inputLabel",
|
||||||
|
srp->nhlfe.label_in);
|
||||||
|
json_object_string_add(json_obj, "outputLabel",
|
||||||
|
label);
|
||||||
|
json_object_string_add(json_obj, "interface",
|
||||||
|
itf ? itf->name : "-");
|
||||||
|
json_object_string_add(json_obj, "nexthop",
|
||||||
|
inet_ntoa(srp->nhlfe.nexthop));
|
||||||
|
json_object_array_add(json_prefix, json_obj);
|
||||||
|
} else {
|
||||||
|
vty_out(vty, "%18s %8u %9s %21s %9s %15s\n",
|
||||||
|
pref, srp->nhlfe.label_in, label,
|
||||||
sid, itf ? itf->name : "-",
|
sid, itf ? itf->name : "-",
|
||||||
inet_ntoa(srp->nhlfe.nexthop));
|
inet_ntoa(srp->nhlfe.nexthop));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) {
|
for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) {
|
||||||
strncpy(pref, inet_ntoa(srl->nhlfe[0].prefv4.prefix), 16);
|
snprintf(pref, 19, "%s/%u",
|
||||||
|
inet_ntoa(srl->nhlfe[0].prefv4.prefix),
|
||||||
|
srl->nhlfe[0].prefv4.prefixlen);
|
||||||
snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[0]);
|
snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[0]);
|
||||||
if (srl->nhlfe[0].label_out == MPLS_LABEL_IMPLICIT_NULL)
|
if (srl->nhlfe[0].label_out == MPLS_LABEL_IMPLICIT_NULL)
|
||||||
sprintf(label, "pop");
|
sprintf(label, "pop");
|
||||||
else
|
else
|
||||||
sprintf(label, "%u", srl->nhlfe[0].label_out);
|
sprintf(label, "%u", srl->nhlfe[0].label_out);
|
||||||
itf = if_lookup_by_index(srl->nhlfe[0].ifindex, VRF_DEFAULT);
|
itf = if_lookup_by_index(srl->nhlfe[0].ifindex, VRF_DEFAULT);
|
||||||
vty_out(vty, "%15s/%u %8u %9s %21s %9s %15s\n", pref,
|
if (json) {
|
||||||
srl->nhlfe[0].prefv4.prefixlen, srl->nhlfe[0].label_in,
|
if (!json_link) {
|
||||||
|
json_link = json_object_new_array();
|
||||||
|
json_object_object_add(json_node,
|
||||||
|
"extendedLink", json_link);
|
||||||
|
}
|
||||||
|
/* Primary Link */
|
||||||
|
json_obj = json_object_new_object();
|
||||||
|
json_object_string_add(json_obj, "prefix", pref);
|
||||||
|
json_object_int_add(json_obj, "sid", srl->sid[0]);
|
||||||
|
json_object_int_add(json_obj, "inputLabel",
|
||||||
|
srl->nhlfe[0].label_in);
|
||||||
|
json_object_string_add(json_obj, "outputLabel",
|
||||||
|
label);
|
||||||
|
json_object_string_add(json_obj, "interface",
|
||||||
|
itf ? itf->name : "-");
|
||||||
|
json_object_string_add(json_obj, "nexthop",
|
||||||
|
inet_ntoa(srl->nhlfe[0].nexthop));
|
||||||
|
json_object_array_add(json_link, json_obj);
|
||||||
|
/* Backup Link */
|
||||||
|
json_obj = json_object_new_object();
|
||||||
|
snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[1]);
|
||||||
|
if (srl->nhlfe[1].label_out == MPLS_LABEL_IMPLICIT_NULL)
|
||||||
|
sprintf(label, "pop");
|
||||||
|
else
|
||||||
|
sprintf(label, "%u", srl->nhlfe[0].label_out);
|
||||||
|
json_object_string_add(json_obj, "prefix", pref);
|
||||||
|
json_object_int_add(json_obj, "sid", srl->sid[1]);
|
||||||
|
json_object_int_add(json_obj, "inputLabel",
|
||||||
|
srl->nhlfe[1].label_in);
|
||||||
|
json_object_string_add(json_obj, "outputLabel",
|
||||||
|
label);
|
||||||
|
json_object_string_add(json_obj, "interface",
|
||||||
|
itf ? itf->name : "-");
|
||||||
|
json_object_string_add(json_obj, "nexthop",
|
||||||
|
inet_ntoa(srl->nhlfe[1].nexthop));
|
||||||
|
json_object_array_add(json_link, json_obj);
|
||||||
|
} else {
|
||||||
|
vty_out(vty, "%18s %8u %9s %21s %9s %15s\n",
|
||||||
|
pref, srl->nhlfe[0].label_in,
|
||||||
label, sid, itf ? itf->name : "-",
|
label, sid, itf ? itf->name : "-",
|
||||||
inet_ntoa(srl->nhlfe[0].nexthop));
|
inet_ntoa(srl->nhlfe[0].nexthop));
|
||||||
snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[1]);
|
snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[1]);
|
||||||
if (srl->nhlfe[1].label_out == MPLS_LABEL_IMPLICIT_NULL)
|
if (srl->nhlfe[1].label_out == MPLS_LABEL_IMPLICIT_NULL)
|
||||||
sprintf(label, "pop");
|
sprintf(label, "pop");
|
||||||
else
|
else
|
||||||
sprintf(label, "%u", srl->nhlfe[0].label_out);
|
sprintf(label, "%u", srl->nhlfe[1].label_out);
|
||||||
vty_out(vty, "%15s/%u %8u %9s %21s %9s %15s\n", pref,
|
vty_out(vty, "%18s %8u %9s %21s %9s %15s\n",
|
||||||
srl->nhlfe[1].prefv4.prefixlen, srl->nhlfe[1].label_in,
|
pref, srl->nhlfe[1].label_in,
|
||||||
label, sid, itf ? itf->name : "-",
|
label, sid, itf ? itf->name : "-",
|
||||||
inet_ntoa(srl->nhlfe[1].nexthop));
|
inet_ntoa(srl->nhlfe[1].nexthop));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (json)
|
||||||
|
json_object_array_add(json, json_node);
|
||||||
|
else
|
||||||
vty_out(vty, "\n");
|
vty_out(vty, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_srdb_entry(struct hash_backet *backet, void *args)
|
static void show_vty_srdb(struct hash_backet *backet, void *args)
|
||||||
{
|
{
|
||||||
struct vty *vty = (struct vty *)args;
|
struct vty *vty = (struct vty *)args;
|
||||||
struct sr_node *srn = (struct sr_node *)backet->data;
|
struct sr_node *srn = (struct sr_node *)backet->data;
|
||||||
|
|
||||||
show_vty_sr_node(vty, srn);
|
show_sr_node(vty, NULL, srn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_json_srdb(struct hash_backet *backet, void *args)
|
||||||
|
{
|
||||||
|
struct json_object *json = (struct json_object *)args;
|
||||||
|
struct sr_node *srn = (struct sr_node *)backet->data;
|
||||||
|
|
||||||
|
show_sr_node(NULL, json, srn);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFUN (show_ip_opsf_srdb,
|
DEFUN (show_ip_opsf_srdb,
|
||||||
show_ip_ospf_srdb_cmd,
|
show_ip_ospf_srdb_cmd,
|
||||||
"show ip ospf database segment-routing [adv-router A.B.C.D|self-originate]",
|
"show ip ospf database segment-routing [adv-router A.B.C.D|self-originate] [json]",
|
||||||
SHOW_STR
|
SHOW_STR
|
||||||
IP_STR
|
IP_STR
|
||||||
OSPF_STR
|
OSPF_STR
|
||||||
@ -2222,23 +2329,41 @@ DEFUN (show_ip_opsf_srdb,
|
|||||||
"Show Segment Routing Data Base\n"
|
"Show Segment Routing Data Base\n"
|
||||||
"Advertising SR node\n"
|
"Advertising SR node\n"
|
||||||
"Advertising SR node ID (as an IP address)\n"
|
"Advertising SR node ID (as an IP address)\n"
|
||||||
"Self-originated SR node\n")
|
"Self-originated SR node\n"
|
||||||
|
JSON_STR)
|
||||||
{
|
{
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
struct in_addr rid;
|
struct in_addr rid;
|
||||||
struct sr_node *srn;
|
struct sr_node *srn;
|
||||||
|
u_char uj = use_json(argc, argv);
|
||||||
|
json_object *json = NULL, *json_node_array = NULL;
|
||||||
|
|
||||||
if (!OspfSR.enabled) {
|
if (!OspfSR.enabled) {
|
||||||
vty_out(vty, "Segment Routing is disabled on this router\n");
|
vty_out(vty, "Segment Routing is disabled on this router\n");
|
||||||
return CMD_WARNING;
|
return CMD_WARNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
vty_out(vty, "\n OSPF Segment Routing database for ID %s\n\n",
|
if (uj) {
|
||||||
|
json = json_object_new_object();
|
||||||
|
json_node_array = json_object_new_array();
|
||||||
|
json_object_string_add(json, "srdbID",
|
||||||
inet_ntoa(OspfSR.self->adv_router));
|
inet_ntoa(OspfSR.self->adv_router));
|
||||||
|
json_object_object_add(json, "srNodes", json_node_array);
|
||||||
|
} else {
|
||||||
|
vty_out(vty,
|
||||||
|
"\n\t\tOSPF Segment Routing database for ID %s\n\n",
|
||||||
|
inet_ntoa(OspfSR.self->adv_router));
|
||||||
|
}
|
||||||
|
|
||||||
if (argv_find(argv, argc, "self-originate", &idx)) {
|
if (argv_find(argv, argc, "self-originate", &idx)) {
|
||||||
srn = OspfSR.self;
|
srn = OspfSR.self;
|
||||||
show_vty_sr_node(vty, srn);
|
show_sr_node(vty, json_node_array, srn);
|
||||||
|
if (uj) {
|
||||||
|
vty_out(vty, "%s\n",
|
||||||
|
json_object_to_json_string_ext(json,
|
||||||
|
JSON_C_TO_STRING_PRETTY));
|
||||||
|
json_object_free(json);
|
||||||
|
}
|
||||||
return CMD_SUCCESS;
|
return CMD_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2252,15 +2377,32 @@ DEFUN (show_ip_opsf_srdb,
|
|||||||
/* Get the SR Node from the SRDB */
|
/* Get the SR Node from the SRDB */
|
||||||
srn = (struct sr_node *)hash_lookup(OspfSR.neighbors,
|
srn = (struct sr_node *)hash_lookup(OspfSR.neighbors,
|
||||||
(void *)&rid);
|
(void *)&rid);
|
||||||
show_vty_sr_node(vty, srn);
|
show_sr_node(vty, json_node_array, srn);
|
||||||
|
if (uj) {
|
||||||
|
vty_out(vty, "%s\n",
|
||||||
|
json_object_to_json_string_ext(json,
|
||||||
|
JSON_C_TO_STRING_PRETTY));
|
||||||
|
json_object_free(json);
|
||||||
|
}
|
||||||
return CMD_SUCCESS;
|
return CMD_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* No parameters have been provided, Iterate through all the SRDB */
|
/* No parameters have been provided, Iterate through all the SRDB */
|
||||||
|
if (uj) {
|
||||||
hash_iterate(
|
hash_iterate(
|
||||||
OspfSR.neighbors,
|
OspfSR.neighbors,
|
||||||
(void (*)(struct hash_backet *, void *))show_srdb_entry,
|
(void (*)(struct hash_backet *, void *))show_json_srdb,
|
||||||
|
(void *)json_node_array);
|
||||||
|
vty_out(vty, "%s\n",
|
||||||
|
json_object_to_json_string_ext(json,
|
||||||
|
JSON_C_TO_STRING_PRETTY));
|
||||||
|
json_object_free(json);
|
||||||
|
} else {
|
||||||
|
hash_iterate(
|
||||||
|
OspfSR.neighbors,
|
||||||
|
(void (*)(struct hash_backet *, void *))show_vty_srdb,
|
||||||
(void *)vty);
|
(void *)vty);
|
||||||
|
}
|
||||||
return CMD_SUCCESS;
|
return CMD_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user