From a49d8c17a5c03a77cd191726fcade0be164daaef Mon Sep 17 00:00:00 2001 From: Rajkamal CV Date: Tue, 8 Sep 2026 17:04:02 +0530 Subject: [PATCH 1/4] RDKB-66541 : Initial code changes for gaurdian Initial port of https://gerrit.teamccp.com/#/c/964212/ --- configure.ac | 1 + source/Makefile.am | 2 +- source/guardian/Makefile.am | 19 ++++ source/guardian/guardian.h | 30 +++++++ source/guardian/guardian_config.c | 139 ++++++++++++++++++++++++++++++ source/guardian/guardian_config.h | 27 ++++++ source/guardian/guardian_flows.c | 66 ++++++++++++++ source/guardian/guardian_flows.h | 25 ++++++ source/guardian/guardian_main.c | 118 +++++++++++++++++++++++++ source/guardian/guardian_ovs.c | 57 ++++++++++++ source/guardian/guardian_ovs.h | 21 +++++ 11 files changed, 504 insertions(+), 1 deletion(-) create mode 100755 source/guardian/Makefile.am create mode 100755 source/guardian/guardian.h create mode 100755 source/guardian/guardian_config.c create mode 100755 source/guardian/guardian_config.h create mode 100755 source/guardian/guardian_flows.c create mode 100755 source/guardian/guardian_flows.h create mode 100755 source/guardian/guardian_main.c create mode 100755 source/guardian/guardian_ovs.c create mode 100755 source/guardian/guardian_ovs.h diff --git a/configure.ac b/configure.ac index 67fc3ca..07d68e5 100644 --- a/configure.ac +++ b/configure.ac @@ -85,6 +85,7 @@ AC_CONFIG_FILES( source/OvsDbApi/Makefile source/OvsAction/Makefile source/OvsAgentApi/Makefile + source/guardian/Makefile ) AC_SUBST(GTEST_ENABLE_FLAG) diff --git a/source/Makefile.am b/source/Makefile.am index 0976efe..ee3e360 100644 --- a/source/Makefile.am +++ b/source/Makefile.am @@ -16,7 +16,7 @@ # SPDX-License-Identifier: Apache-2.0 # -SUBDIRS = OvsAgentSsp OvsAction OvsDbApi OvsAgentApi OvsAgentCore +SUBDIRS = OvsAgentSsp OvsAction OvsDbApi OvsAgentApi OvsAgentCore guardian if WITH_GTEST_SUPPORT SUBDIRS += test diff --git a/source/guardian/Makefile.am b/source/guardian/Makefile.am new file mode 100755 index 0000000..cfe8002 --- /dev/null +++ b/source/guardian/Makefile.am @@ -0,0 +1,19 @@ +# +# Guardian OpenFlow policy agent (native C port of guardian.sh). +# SPDX-License-Identifier: Apache-2.0 +# + +bin_PROGRAMS = guardian + +guardian_SOURCES = \ + guardian_main.c \ + guardian_config.c \ + guardian_flows.c \ + guardian_ovs.c + +guardian_CPPFLAGS = -Wall -Werror -I$(top_srcdir)/source/include + +# Initial port uses ovs-ofctl (Option B) and needs no OVS lib link. +# TODO(Option A): add $(OPENVSWITCH_CFLAGS)/$(OPENVSWITCH_LIBS) to link +# libopenvswitch for in-memory delta via vconn_dump_flows(). +guardian_LDADD = diff --git a/source/guardian/guardian.h b/source/guardian/guardian.h new file mode 100755 index 0000000..03d11df --- /dev/null +++ b/source/guardian/guardian.h @@ -0,0 +1,30 @@ +/* + * Guardian: native C port of guardian.sh. + * Shared constants and core data structures. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_H +#define GUARDIAN_H + +#include +#include + +/* Match the live values used by guardian.sh so existing tooling keeps working. */ +#define GUARDIAN_BRIDGE "brlan0" +#define GUARDIAN_COOKIE 0x9110ULL +#define GUARDIAN_CT_ZONE 9110 + +/* Commands accepted on the CLI. */ +enum guardian_cmd { + GUARDIAN_CMD_UNKNOWN = 0, + GUARDIAN_CMD_APPLY, + GUARDIAN_CMD_DRY_RUN, + GUARDIAN_CMD_CLEAR, + GUARDIAN_CMD_SHOW, + GUARDIAN_CMD_STATS, + GUARDIAN_CMD_DIAGNOSTICS, + GUARDIAN_CMD_LOAD_TEST, +}; + +#endif /* GUARDIAN_H */ diff --git a/source/guardian/guardian_config.c b/source/guardian/guardian_config.c new file mode 100755 index 0000000..71a59ff --- /dev/null +++ b/source/guardian/guardian_config.c @@ -0,0 +1,139 @@ +/* + * Guardian config parsing + delta computation. + * + * Initial port scaffold: parses the INI-style guardian.cfg into in-memory + * tables (groups, devices, services, macros, policies, defaults). The flow + * generation (guardian_flows.c) consumes these tables instead of forking awk + * per line as guardian.sh did. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "guardian_config.h" + +#include +#include +#include +#include +#include + +/* TODO: replace fixed caps with dynamic arrays once parsing is fleshed out. */ +#define MAX_GROUPS 64 +#define MAX_DEVICES 512 +#define MAX_NAME 64 + +struct group_entry { + char name[MAX_NAME]; + uint32_t id; +}; + +struct device_entry { + char mac[MAX_NAME]; + char group[MAX_NAME]; +}; + +struct guardian_config { + struct group_entry groups[MAX_GROUPS]; + size_t n_groups; + struct device_entry devices[MAX_DEVICES]; + size_t n_devices; + bool stateful; + /* TODO: services, service_macros, group/intra/device policy, other defaults. */ +}; + +/* Strip inline " # comment", leading/trailing whitespace; returns trimmed + * start (may be empty). Modifies buf in place. */ +static char *trim_line(char *buf) +{ + /* Drop an inline comment preceded by whitespace, or a full-line comment. */ + char *h = buf; + while (*h && isspace((unsigned char)*h)) h++; + if (*h == '#') { *h = '\0'; return h; } + + for (char *p = buf; *p; p++) { + if (*p == '#' && p > buf && isspace((unsigned char)p[-1])) { *p = '\0'; break; } + } + /* Trim trailing whitespace. */ + size_t len = strlen(h); + while (len > 0 && isspace((unsigned char)h[len - 1])) h[--len] = '\0'; + return h; +} + +struct guardian_config *guardian_config_load(const char *path, char **err) +{ + FILE *fp = fopen(path, "r"); + if (!fp) { + if (err) { + char msg[256]; + snprintf(msg, sizeof msg, "cannot open '%s'", path); + *err = strdup(msg); + } + return NULL; + } + + struct guardian_config *cfg = calloc(1, sizeof *cfg); + if (!cfg) { fclose(fp); if (err) *err = strdup("out of memory"); return NULL; } + + char line[512]; + char section[MAX_NAME] = ""; + + while (fgets(line, sizeof line, fp)) { + char *s = trim_line(line); + if (*s == '\0') continue; + + if (*s == '[') { + char *end = strchr(s, ']'); + if (end) { + size_t n = (size_t)(end - s - 1); + if (n >= sizeof section) n = sizeof section - 1; + memcpy(section, s + 1, n); + section[n] = '\0'; + } + continue; + } + + if (!strcasecmp(section, "groups")) { + char name[MAX_NAME]; unsigned long id; + if (sscanf(s, "%63s %lu", name, &id) == 2 && cfg->n_groups < MAX_GROUPS) { + struct group_entry *g = &cfg->groups[cfg->n_groups++]; + snprintf(g->name, sizeof g->name, "%s", name); + g->id = (uint32_t)id; + } + } else if (!strcasecmp(section, "devices")) { + char mac[MAX_NAME], grp[MAX_NAME]; + if (sscanf(s, "%63s %63s", mac, grp) == 2 && cfg->n_devices < MAX_DEVICES) { + struct device_entry *d = &cfg->devices[cfg->n_devices++]; + snprintf(d->mac, sizeof d->mac, "%s", mac); + snprintf(d->group, sizeof d->group, "%s", grp); + } + } else if (!strcasecmp(section, "defaults")) { + char key[MAX_NAME], val[MAX_NAME]; + if (sscanf(s, "%63s %63s", key, val) == 2 && + !strcasecmp(key, "stateful")) { + cfg->stateful = !strcasecmp(val, "true"); + } + } + /* TODO: services, service_macros, *_policy sections. */ + } + + fclose(fp); + return cfg; +} + +void guardian_config_free(struct guardian_config *cfg) +{ + free(cfg); +} + +uint32_t guardian_config_group_id(const struct guardian_config *cfg, + const char *name) +{ + for (size_t i = 0; i < cfg->n_groups; i++) + if (!strcasecmp(cfg->groups[i].name, name)) + return cfg->groups[i].id; + return 0; +} + +bool guardian_config_stateful(const struct guardian_config *cfg) +{ + return cfg->stateful; +} diff --git a/source/guardian/guardian_config.h b/source/guardian/guardian_config.h new file mode 100755 index 0000000..4feb859 --- /dev/null +++ b/source/guardian/guardian_config.h @@ -0,0 +1,27 @@ +/* + * Guardian config parsing + delta computation. + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_CONFIG_H +#define GUARDIAN_CONFIG_H + +#include +#include +#include + +/* Parsed configuration. Populated from the INI-style guardian.cfg. */ +struct guardian_config; + +/* Load and validate a config file. Returns NULL and sets *err (malloc'd) on + * failure; caller frees the returned config with guardian_config_free(). */ +struct guardian_config *guardian_config_load(const char *path, char **err); +void guardian_config_free(struct guardian_config *cfg); + +/* Resolve a group name to its numeric bitmask id; returns 0 if unknown. */ +uint32_t guardian_config_group_id(const struct guardian_config *cfg, + const char *name); + +/* Whether stateful (conntrack) mode is enabled in [defaults]. */ +bool guardian_config_stateful(const struct guardian_config *cfg); + +#endif /* GUARDIAN_CONFIG_H */ diff --git a/source/guardian/guardian_flows.c b/source/guardian/guardian_flows.c new file mode 100755 index 0000000..29ccbde --- /dev/null +++ b/source/guardian/guardian_flows.c @@ -0,0 +1,66 @@ +/* + * Guardian policy -> OpenFlow flow translation (port of gen_flows). + * + * Initial scaffold: emits the table-0/table-1 source/dest classification flows + * from the parsed config. Remaining tables (policy hierarchy, stateful ct path, + * multicast delivery) are ported incrementally and validated against + * `guardian.sh dry-run`. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "guardian_flows.h" +#include "guardian.h" +#include "guardian_config.h" + +#include +#include +#include + +/* Access to the parsed tables. Kept internal to config.c today; the flow + * builder currently drives generation through the public accessors and a small + * device iterator added here as the port grows. For now we expose just enough + * via accessor calls. */ + +static int flowset_push(struct guardian_flowset *fs, const char *line) +{ + char **grown = realloc(fs->lines, (fs->n + 1) * sizeof *fs->lines); + if (!grown) return -1; + fs->lines = grown; + fs->lines[fs->n] = strdup(line); + if (!fs->lines[fs->n]) return -1; + fs->n++; + return 0; +} + +struct guardian_flowset *guardian_flows_generate(const struct guardian_config *cfg) +{ + (void)cfg; + struct guardian_flowset *fs = calloc(1, sizeof *fs); + if (!fs) return NULL; + + char buf[512]; + + /* Unmanaged sources still traverse the pipeline for policy evaluation. */ + snprintf(buf, sizeof buf, + "cookie=0x%llx,table=0,priority=1,actions=resubmit(,1)", + (unsigned long long)GUARDIAN_COOKIE); + if (flowset_push(fs, buf)) goto fail; + + /* TODO: per-device table-0 (dl_src->reg0) and table-1 (dl_dst->reg1) flows, + * then tables 2/3/4 policy hierarchy. Drives off cfg's group/device tables. */ + + return fs; + +fail: + guardian_flowset_free(fs); + return NULL; +} + +void guardian_flowset_free(struct guardian_flowset *fs) +{ + if (!fs) return; + for (size_t i = 0; i < fs->n; i++) + free(fs->lines[i]); + free(fs->lines); + free(fs); +} diff --git a/source/guardian/guardian_flows.h b/source/guardian/guardian_flows.h new file mode 100755 index 0000000..1a09b77 --- /dev/null +++ b/source/guardian/guardian_flows.h @@ -0,0 +1,25 @@ +/* + * Guardian policy -> OpenFlow flow translation (port of gen_flows). + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_FLOWS_H +#define GUARDIAN_FLOWS_H + +#include + +struct guardian_config; + +/* A desired flow set, as flow-mod text lines (one flow per line), matching the + * format guardian.sh emits. Kept as text for the initial port so it can be + * validated against `guardian.sh dry-run` before switching to in-memory + * ofputil_flow_mod encoding. */ +struct guardian_flowset { + char **lines; + size_t n; +}; + +/* Build the desired flow set from a parsed config. Returns NULL on error. */ +struct guardian_flowset *guardian_flows_generate(const struct guardian_config *cfg); +void guardian_flowset_free(struct guardian_flowset *fs); + +#endif /* GUARDIAN_FLOWS_H */ diff --git a/source/guardian/guardian_main.c b/source/guardian/guardian_main.c new file mode 100755 index 0000000..4e574e0 --- /dev/null +++ b/source/guardian/guardian_main.c @@ -0,0 +1,118 @@ +/* + * Guardian: native C port of guardian.sh. + * CLI argument parsing and command dispatch. + * + * guardian [config_file] + * + * Only apply/dry-run read the config; the others act on the live bridge. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include "guardian.h" +#include "guardian_config.h" +#include "guardian_flows.h" +#include "guardian_ovs.h" + +static const char *const DEFAULT_CONFIG = "guardian.cfg"; + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [config_file] {apply|dry-run|clear|show|stats|diagnostics|load-test}\n" + " Config file defaults to '%s' if omitted.\n", + prog, DEFAULT_CONFIG); +} + +static enum guardian_cmd parse_cmd(const char *s) +{ + if (!strcmp(s, "apply")) return GUARDIAN_CMD_APPLY; + if (!strcmp(s, "dry-run")) return GUARDIAN_CMD_DRY_RUN; + if (!strcmp(s, "clear")) return GUARDIAN_CMD_CLEAR; + if (!strcmp(s, "show")) return GUARDIAN_CMD_SHOW; + if (!strcmp(s, "stats")) return GUARDIAN_CMD_STATS; + if (!strcmp(s, "diagnostics")) return GUARDIAN_CMD_DIAGNOSTICS; + if (!strcmp(s, "load-test")) return GUARDIAN_CMD_LOAD_TEST; + return GUARDIAN_CMD_UNKNOWN; +} + +/* Build the desired flow set from the config; caller frees. */ +static struct guardian_flowset *load_desired(const char *config) +{ + char *err = NULL; + struct guardian_config *cfg = guardian_config_load(config, &err); + if (!cfg) { + fprintf(stderr, "guardian: config error: %s\n", err ? err : "unknown"); + free(err); + return NULL; + } + struct guardian_flowset *fs = guardian_flows_generate(cfg); + guardian_config_free(cfg); + if (!fs) { + fprintf(stderr, "guardian: failed to generate flows\n"); + return NULL; + } + return fs; +} + +int main(int argc, char *argv[]) +{ + const char *config = DEFAULT_CONFIG; + const char *cmd_str; + + /* Accept "" or " ". */ + if (argc == 2) { + cmd_str = argv[1]; + } else if (argc == 3) { + config = argv[1]; + cmd_str = argv[2]; + } else { + usage(argv[0]); + return 2; + } + + enum guardian_cmd cmd = parse_cmd(cmd_str); + if (cmd == GUARDIAN_CMD_UNKNOWN) { + usage(argv[0]); + return 2; + } + + switch (cmd) { + case GUARDIAN_CMD_APPLY: { + struct guardian_flowset *fs = load_desired(config); + if (!fs) return 1; + int rc = guardian_ovs_apply(GUARDIAN_BRIDGE, fs); + guardian_flowset_free(fs); + if (rc == 0) + printf("Guardian policy applied to %s.\n", GUARDIAN_BRIDGE); + return rc ? 1 : 0; + } + case GUARDIAN_CMD_DRY_RUN: { + struct guardian_flowset *fs = load_desired(config); + if (!fs) return 1; + for (size_t i = 0; i < fs->n; i++) + puts(fs->lines[i]); + guardian_flowset_free(fs); + return 0; + } + case GUARDIAN_CMD_CLEAR: + if (guardian_ovs_clear(GUARDIAN_BRIDGE) == 0) { + printf("Guardian policy removed from %s.\n", GUARDIAN_BRIDGE); + return 0; + } + return 1; + case GUARDIAN_CMD_SHOW: + return guardian_ovs_show(GUARDIAN_BRIDGE) ? 1 : 0; + case GUARDIAN_CMD_STATS: + case GUARDIAN_CMD_DIAGNOSTICS: + case GUARDIAN_CMD_LOAD_TEST: + fprintf(stderr, "guardian: '%s' not yet implemented in the C port\n", cmd_str); + return 1; + default: + usage(argv[0]); + return 2; + } +} diff --git a/source/guardian/guardian_ovs.c b/source/guardian/guardian_ovs.c new file mode 100755 index 0000000..0a11b4a --- /dev/null +++ b/source/guardian/guardian_ovs.c @@ -0,0 +1,57 @@ +/* + * Guardian OVS I/O. + * + * Initial scaffold uses a single `ovs-ofctl` invocation (Option B in + * PORTING.md): the whole desired flow set is streamed once via stdin, removing + * the per-line forking of guardian.sh. This keeps the port buildable without + * the OVS staticdev sysroot. The follow-up step swaps this for libopenvswitch + * (vconn_open + vconn_dump_flows delta + ofputil_encode_flow_mod) per PORTING.md + * section 2 Option A. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "guardian_ovs.h" +#include "guardian.h" + +#include +#include +#include + +int guardian_ovs_apply(const char *bridge, const struct guardian_flowset *desired) +{ + /* Full-table replace for the initial port: clear our cookie, then add the + * desired set in one add-flows call. Delta comes with the Option A rewrite. */ + if (guardian_ovs_clear(bridge) != 0) + return -1; + + char cmd[256]; + snprintf(cmd, sizeof cmd, "ovs-ofctl add-flows %s -", bridge); + FILE *p = popen(cmd, "w"); + if (!p) { + perror("guardian: popen(ovs-ofctl add-flows)"); + return -1; + } + for (size_t i = 0; i < desired->n; i++) + fprintf(p, "%s\n", desired->lines[i]); + + int rc = pclose(p); + return rc == 0 ? 0 : -1; +} + +int guardian_ovs_clear(const char *bridge) +{ + char cmd[256]; + snprintf(cmd, sizeof cmd, + "ovs-ofctl del-flows %s \"cookie=0x%llx/-1\"", + bridge, (unsigned long long)GUARDIAN_COOKIE); + return system(cmd) == 0 ? 0 : -1; +} + +int guardian_ovs_show(const char *bridge) +{ + char cmd[256]; + snprintf(cmd, sizeof cmd, + "ovs-ofctl dump-flows %s | grep -i 0x%llx || echo 'No Guardian flows.'", + bridge, (unsigned long long)GUARDIAN_COOKIE); + return system(cmd) == 0 ? 0 : -1; +} diff --git a/source/guardian/guardian_ovs.h b/source/guardian/guardian_ovs.h new file mode 100755 index 0000000..9258917 --- /dev/null +++ b/source/guardian/guardian_ovs.h @@ -0,0 +1,21 @@ +/* + * Guardian OVS I/O: connect to the bridge, dump live flows, apply add/del. + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_OVS_H +#define GUARDIAN_OVS_H + +#include "guardian_flows.h" + +/* Apply the desired flow set to the bridge using a delta against the live + * flows (cookie GUARDIAN_COOKIE): only changed flows are added/deleted. + * Returns 0 on success. */ +int guardian_ovs_apply(const char *bridge, const struct guardian_flowset *desired); + +/* Remove all Guardian flows (cookie GUARDIAN_COOKIE) from the bridge. */ +int guardian_ovs_clear(const char *bridge); + +/* Print active Guardian flows (equivalent to guardian.sh show). */ +int guardian_ovs_show(const char *bridge); + +#endif /* GUARDIAN_OVS_H */ From 9ab6c86434019a13cd3b4b1bc0c0f6e522f310f2 Mon Sep 17 00:00:00 2001 From: Rajkamal CV Date: Tue, 8 Sep 2026 17:26:24 +0530 Subject: [PATCH 2/4] RDKB-66541 : Use libopenvswtich APIs --- configure.ac | 1 + source/guardian/Makefile.am | 36 ++-- source/guardian/guardian.h | 60 +++---- source/guardian/guardian_config.c | 278 +++++++++++++++--------------- source/guardian/guardian_config.h | 54 +++--- source/guardian/guardian_flows.c | 132 +++++++------- source/guardian/guardian_flows.h | 50 +++--- source/guardian/guardian_main.c | 236 ++++++++++++------------- source/guardian/guardian_ovs.c | 241 ++++++++++++++++++++------ source/guardian/guardian_ovs.h | 42 ++--- 10 files changed, 628 insertions(+), 502 deletions(-) diff --git a/configure.ac b/configure.ac index 07d68e5..97747a3 100644 --- a/configure.ac +++ b/configure.ac @@ -76,6 +76,7 @@ AC_C_INLINE AC_FUNC_MALLOC PKG_CHECK_MODULES([DBUS],[dbus-1 >= 1.6.18]) +PKG_CHECK_MODULES([OPENVSWITCH],[libopenvswitch]) AC_CONFIG_FILES( Makefile diff --git a/source/guardian/Makefile.am b/source/guardian/Makefile.am index cfe8002..7dd36ee 100755 --- a/source/guardian/Makefile.am +++ b/source/guardian/Makefile.am @@ -1,19 +1,17 @@ -# -# Guardian OpenFlow policy agent (native C port of guardian.sh). -# SPDX-License-Identifier: Apache-2.0 -# - -bin_PROGRAMS = guardian - -guardian_SOURCES = \ - guardian_main.c \ - guardian_config.c \ - guardian_flows.c \ - guardian_ovs.c - -guardian_CPPFLAGS = -Wall -Werror -I$(top_srcdir)/source/include - -# Initial port uses ovs-ofctl (Option B) and needs no OVS lib link. -# TODO(Option A): add $(OPENVSWITCH_CFLAGS)/$(OPENVSWITCH_LIBS) to link -# libopenvswitch for in-memory delta via vconn_dump_flows(). -guardian_LDADD = +# +# Guardian OpenFlow policy agent (native C port of guardian.sh). +# SPDX-License-Identifier: Apache-2.0 +# + +bin_PROGRAMS = guardian + +guardian_SOURCES = \ + guardian_main.c \ + guardian_config.c \ + guardian_flows.c \ + guardian_ovs.c + +guardian_CPPFLAGS = -Wall -Werror -I$(top_srcdir)/source/include $(OPENVSWITCH_CFLAGS) + +# Links libopenvswitch for direct OpenFlow I/O (vconn/ofputil) -- no ovs-ofctl fork. +guardian_LDADD = $(OPENVSWITCH_LIBS) diff --git a/source/guardian/guardian.h b/source/guardian/guardian.h index 03d11df..f76e2fb 100755 --- a/source/guardian/guardian.h +++ b/source/guardian/guardian.h @@ -1,30 +1,30 @@ -/* - * Guardian: native C port of guardian.sh. - * Shared constants and core data structures. - * - * SPDX-License-Identifier: Apache-2.0 - */ -#ifndef GUARDIAN_H -#define GUARDIAN_H - -#include -#include - -/* Match the live values used by guardian.sh so existing tooling keeps working. */ -#define GUARDIAN_BRIDGE "brlan0" -#define GUARDIAN_COOKIE 0x9110ULL -#define GUARDIAN_CT_ZONE 9110 - -/* Commands accepted on the CLI. */ -enum guardian_cmd { - GUARDIAN_CMD_UNKNOWN = 0, - GUARDIAN_CMD_APPLY, - GUARDIAN_CMD_DRY_RUN, - GUARDIAN_CMD_CLEAR, - GUARDIAN_CMD_SHOW, - GUARDIAN_CMD_STATS, - GUARDIAN_CMD_DIAGNOSTICS, - GUARDIAN_CMD_LOAD_TEST, -}; - -#endif /* GUARDIAN_H */ +/* + * Guardian: native C port of guardian.sh. + * Shared constants and core data structures. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_H +#define GUARDIAN_H + +#include +#include + +/* Match the live values used by guardian.sh so existing tooling keeps working. */ +#define GUARDIAN_BRIDGE "brlan0" +#define GUARDIAN_COOKIE 0x9110ULL +#define GUARDIAN_CT_ZONE 9110 + +/* Commands accepted on the CLI. */ +enum guardian_cmd { + GUARDIAN_CMD_UNKNOWN = 0, + GUARDIAN_CMD_APPLY, + GUARDIAN_CMD_DRY_RUN, + GUARDIAN_CMD_CLEAR, + GUARDIAN_CMD_SHOW, + GUARDIAN_CMD_STATS, + GUARDIAN_CMD_DIAGNOSTICS, + GUARDIAN_CMD_LOAD_TEST, +}; + +#endif /* GUARDIAN_H */ diff --git a/source/guardian/guardian_config.c b/source/guardian/guardian_config.c index 71a59ff..3125e61 100755 --- a/source/guardian/guardian_config.c +++ b/source/guardian/guardian_config.c @@ -1,139 +1,139 @@ -/* - * Guardian config parsing + delta computation. - * - * Initial port scaffold: parses the INI-style guardian.cfg into in-memory - * tables (groups, devices, services, macros, policies, defaults). The flow - * generation (guardian_flows.c) consumes these tables instead of forking awk - * per line as guardian.sh did. - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include "guardian_config.h" - -#include -#include -#include -#include -#include - -/* TODO: replace fixed caps with dynamic arrays once parsing is fleshed out. */ -#define MAX_GROUPS 64 -#define MAX_DEVICES 512 -#define MAX_NAME 64 - -struct group_entry { - char name[MAX_NAME]; - uint32_t id; -}; - -struct device_entry { - char mac[MAX_NAME]; - char group[MAX_NAME]; -}; - -struct guardian_config { - struct group_entry groups[MAX_GROUPS]; - size_t n_groups; - struct device_entry devices[MAX_DEVICES]; - size_t n_devices; - bool stateful; - /* TODO: services, service_macros, group/intra/device policy, other defaults. */ -}; - -/* Strip inline " # comment", leading/trailing whitespace; returns trimmed - * start (may be empty). Modifies buf in place. */ -static char *trim_line(char *buf) -{ - /* Drop an inline comment preceded by whitespace, or a full-line comment. */ - char *h = buf; - while (*h && isspace((unsigned char)*h)) h++; - if (*h == '#') { *h = '\0'; return h; } - - for (char *p = buf; *p; p++) { - if (*p == '#' && p > buf && isspace((unsigned char)p[-1])) { *p = '\0'; break; } - } - /* Trim trailing whitespace. */ - size_t len = strlen(h); - while (len > 0 && isspace((unsigned char)h[len - 1])) h[--len] = '\0'; - return h; -} - -struct guardian_config *guardian_config_load(const char *path, char **err) -{ - FILE *fp = fopen(path, "r"); - if (!fp) { - if (err) { - char msg[256]; - snprintf(msg, sizeof msg, "cannot open '%s'", path); - *err = strdup(msg); - } - return NULL; - } - - struct guardian_config *cfg = calloc(1, sizeof *cfg); - if (!cfg) { fclose(fp); if (err) *err = strdup("out of memory"); return NULL; } - - char line[512]; - char section[MAX_NAME] = ""; - - while (fgets(line, sizeof line, fp)) { - char *s = trim_line(line); - if (*s == '\0') continue; - - if (*s == '[') { - char *end = strchr(s, ']'); - if (end) { - size_t n = (size_t)(end - s - 1); - if (n >= sizeof section) n = sizeof section - 1; - memcpy(section, s + 1, n); - section[n] = '\0'; - } - continue; - } - - if (!strcasecmp(section, "groups")) { - char name[MAX_NAME]; unsigned long id; - if (sscanf(s, "%63s %lu", name, &id) == 2 && cfg->n_groups < MAX_GROUPS) { - struct group_entry *g = &cfg->groups[cfg->n_groups++]; - snprintf(g->name, sizeof g->name, "%s", name); - g->id = (uint32_t)id; - } - } else if (!strcasecmp(section, "devices")) { - char mac[MAX_NAME], grp[MAX_NAME]; - if (sscanf(s, "%63s %63s", mac, grp) == 2 && cfg->n_devices < MAX_DEVICES) { - struct device_entry *d = &cfg->devices[cfg->n_devices++]; - snprintf(d->mac, sizeof d->mac, "%s", mac); - snprintf(d->group, sizeof d->group, "%s", grp); - } - } else if (!strcasecmp(section, "defaults")) { - char key[MAX_NAME], val[MAX_NAME]; - if (sscanf(s, "%63s %63s", key, val) == 2 && - !strcasecmp(key, "stateful")) { - cfg->stateful = !strcasecmp(val, "true"); - } - } - /* TODO: services, service_macros, *_policy sections. */ - } - - fclose(fp); - return cfg; -} - -void guardian_config_free(struct guardian_config *cfg) -{ - free(cfg); -} - -uint32_t guardian_config_group_id(const struct guardian_config *cfg, - const char *name) -{ - for (size_t i = 0; i < cfg->n_groups; i++) - if (!strcasecmp(cfg->groups[i].name, name)) - return cfg->groups[i].id; - return 0; -} - -bool guardian_config_stateful(const struct guardian_config *cfg) -{ - return cfg->stateful; -} +/* + * Guardian config parsing + delta computation. + * + * Initial port scaffold: parses the INI-style guardian.cfg into in-memory + * tables (groups, devices, services, macros, policies, defaults). The flow + * generation (guardian_flows.c) consumes these tables instead of forking awk + * per line as guardian.sh did. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "guardian_config.h" + +#include +#include +#include +#include +#include + +/* TODO: replace fixed caps with dynamic arrays once parsing is fleshed out. */ +#define MAX_GROUPS 64 +#define MAX_DEVICES 512 +#define MAX_NAME 64 + +struct group_entry { + char name[MAX_NAME]; + uint32_t id; +}; + +struct device_entry { + char mac[MAX_NAME]; + char group[MAX_NAME]; +}; + +struct guardian_config { + struct group_entry groups[MAX_GROUPS]; + size_t n_groups; + struct device_entry devices[MAX_DEVICES]; + size_t n_devices; + bool stateful; + /* TODO: services, service_macros, group/intra/device policy, other defaults. */ +}; + +/* Strip inline " # comment", leading/trailing whitespace; returns trimmed + * start (may be empty). Modifies buf in place. */ +static char *trim_line(char *buf) +{ + /* Drop an inline comment preceded by whitespace, or a full-line comment. */ + char *h = buf; + while (*h && isspace((unsigned char)*h)) h++; + if (*h == '#') { *h = '\0'; return h; } + + for (char *p = buf; *p; p++) { + if (*p == '#' && p > buf && isspace((unsigned char)p[-1])) { *p = '\0'; break; } + } + /* Trim trailing whitespace. */ + size_t len = strlen(h); + while (len > 0 && isspace((unsigned char)h[len - 1])) h[--len] = '\0'; + return h; +} + +struct guardian_config *guardian_config_load(const char *path, char **err) +{ + FILE *fp = fopen(path, "r"); + if (!fp) { + if (err) { + char msg[256]; + snprintf(msg, sizeof msg, "cannot open '%s'", path); + *err = strdup(msg); + } + return NULL; + } + + struct guardian_config *cfg = calloc(1, sizeof *cfg); + if (!cfg) { fclose(fp); if (err) *err = strdup("out of memory"); return NULL; } + + char line[512]; + char section[MAX_NAME] = ""; + + while (fgets(line, sizeof line, fp)) { + char *s = trim_line(line); + if (*s == '\0') continue; + + if (*s == '[') { + char *end = strchr(s, ']'); + if (end) { + size_t n = (size_t)(end - s - 1); + if (n >= sizeof section) n = sizeof section - 1; + memcpy(section, s + 1, n); + section[n] = '\0'; + } + continue; + } + + if (!strcasecmp(section, "groups")) { + char name[MAX_NAME]; unsigned long id; + if (sscanf(s, "%63s %lu", name, &id) == 2 && cfg->n_groups < MAX_GROUPS) { + struct group_entry *g = &cfg->groups[cfg->n_groups++]; + snprintf(g->name, sizeof g->name, "%s", name); + g->id = (uint32_t)id; + } + } else if (!strcasecmp(section, "devices")) { + char mac[MAX_NAME], grp[MAX_NAME]; + if (sscanf(s, "%63s %63s", mac, grp) == 2 && cfg->n_devices < MAX_DEVICES) { + struct device_entry *d = &cfg->devices[cfg->n_devices++]; + snprintf(d->mac, sizeof d->mac, "%s", mac); + snprintf(d->group, sizeof d->group, "%s", grp); + } + } else if (!strcasecmp(section, "defaults")) { + char key[MAX_NAME], val[MAX_NAME]; + if (sscanf(s, "%63s %63s", key, val) == 2 && + !strcasecmp(key, "stateful")) { + cfg->stateful = !strcasecmp(val, "true"); + } + } + /* TODO: services, service_macros, *_policy sections. */ + } + + fclose(fp); + return cfg; +} + +void guardian_config_free(struct guardian_config *cfg) +{ + free(cfg); +} + +uint32_t guardian_config_group_id(const struct guardian_config *cfg, + const char *name) +{ + for (size_t i = 0; i < cfg->n_groups; i++) + if (!strcasecmp(cfg->groups[i].name, name)) + return cfg->groups[i].id; + return 0; +} + +bool guardian_config_stateful(const struct guardian_config *cfg) +{ + return cfg->stateful; +} diff --git a/source/guardian/guardian_config.h b/source/guardian/guardian_config.h index 4feb859..8dc3163 100755 --- a/source/guardian/guardian_config.h +++ b/source/guardian/guardian_config.h @@ -1,27 +1,27 @@ -/* - * Guardian config parsing + delta computation. - * SPDX-License-Identifier: Apache-2.0 - */ -#ifndef GUARDIAN_CONFIG_H -#define GUARDIAN_CONFIG_H - -#include -#include -#include - -/* Parsed configuration. Populated from the INI-style guardian.cfg. */ -struct guardian_config; - -/* Load and validate a config file. Returns NULL and sets *err (malloc'd) on - * failure; caller frees the returned config with guardian_config_free(). */ -struct guardian_config *guardian_config_load(const char *path, char **err); -void guardian_config_free(struct guardian_config *cfg); - -/* Resolve a group name to its numeric bitmask id; returns 0 if unknown. */ -uint32_t guardian_config_group_id(const struct guardian_config *cfg, - const char *name); - -/* Whether stateful (conntrack) mode is enabled in [defaults]. */ -bool guardian_config_stateful(const struct guardian_config *cfg); - -#endif /* GUARDIAN_CONFIG_H */ +/* + * Guardian config parsing + delta computation. + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_CONFIG_H +#define GUARDIAN_CONFIG_H + +#include +#include +#include + +/* Parsed configuration. Populated from the INI-style guardian.cfg. */ +struct guardian_config; + +/* Load and validate a config file. Returns NULL and sets *err (malloc'd) on + * failure; caller frees the returned config with guardian_config_free(). */ +struct guardian_config *guardian_config_load(const char *path, char **err); +void guardian_config_free(struct guardian_config *cfg); + +/* Resolve a group name to its numeric bitmask id; returns 0 if unknown. */ +uint32_t guardian_config_group_id(const struct guardian_config *cfg, + const char *name); + +/* Whether stateful (conntrack) mode is enabled in [defaults]. */ +bool guardian_config_stateful(const struct guardian_config *cfg); + +#endif /* GUARDIAN_CONFIG_H */ diff --git a/source/guardian/guardian_flows.c b/source/guardian/guardian_flows.c index 29ccbde..cab1c83 100755 --- a/source/guardian/guardian_flows.c +++ b/source/guardian/guardian_flows.c @@ -1,66 +1,66 @@ -/* - * Guardian policy -> OpenFlow flow translation (port of gen_flows). - * - * Initial scaffold: emits the table-0/table-1 source/dest classification flows - * from the parsed config. Remaining tables (policy hierarchy, stateful ct path, - * multicast delivery) are ported incrementally and validated against - * `guardian.sh dry-run`. - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include "guardian_flows.h" -#include "guardian.h" -#include "guardian_config.h" - -#include -#include -#include - -/* Access to the parsed tables. Kept internal to config.c today; the flow - * builder currently drives generation through the public accessors and a small - * device iterator added here as the port grows. For now we expose just enough - * via accessor calls. */ - -static int flowset_push(struct guardian_flowset *fs, const char *line) -{ - char **grown = realloc(fs->lines, (fs->n + 1) * sizeof *fs->lines); - if (!grown) return -1; - fs->lines = grown; - fs->lines[fs->n] = strdup(line); - if (!fs->lines[fs->n]) return -1; - fs->n++; - return 0; -} - -struct guardian_flowset *guardian_flows_generate(const struct guardian_config *cfg) -{ - (void)cfg; - struct guardian_flowset *fs = calloc(1, sizeof *fs); - if (!fs) return NULL; - - char buf[512]; - - /* Unmanaged sources still traverse the pipeline for policy evaluation. */ - snprintf(buf, sizeof buf, - "cookie=0x%llx,table=0,priority=1,actions=resubmit(,1)", - (unsigned long long)GUARDIAN_COOKIE); - if (flowset_push(fs, buf)) goto fail; - - /* TODO: per-device table-0 (dl_src->reg0) and table-1 (dl_dst->reg1) flows, - * then tables 2/3/4 policy hierarchy. Drives off cfg's group/device tables. */ - - return fs; - -fail: - guardian_flowset_free(fs); - return NULL; -} - -void guardian_flowset_free(struct guardian_flowset *fs) -{ - if (!fs) return; - for (size_t i = 0; i < fs->n; i++) - free(fs->lines[i]); - free(fs->lines); - free(fs); -} +/* + * Guardian policy -> OpenFlow flow translation (port of gen_flows). + * + * Initial scaffold: emits the table-0/table-1 source/dest classification flows + * from the parsed config. Remaining tables (policy hierarchy, stateful ct path, + * multicast delivery) are ported incrementally and validated against + * `guardian.sh dry-run`. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "guardian_flows.h" +#include "guardian.h" +#include "guardian_config.h" + +#include +#include +#include + +/* Access to the parsed tables. Kept internal to config.c today; the flow + * builder currently drives generation through the public accessors and a small + * device iterator added here as the port grows. For now we expose just enough + * via accessor calls. */ + +static int flowset_push(struct guardian_flowset *fs, const char *line) +{ + char **grown = realloc(fs->lines, (fs->n + 1) * sizeof *fs->lines); + if (!grown) return -1; + fs->lines = grown; + fs->lines[fs->n] = strdup(line); + if (!fs->lines[fs->n]) return -1; + fs->n++; + return 0; +} + +struct guardian_flowset *guardian_flows_generate(const struct guardian_config *cfg) +{ + (void)cfg; + struct guardian_flowset *fs = calloc(1, sizeof *fs); + if (!fs) return NULL; + + char buf[512]; + + /* Unmanaged sources still traverse the pipeline for policy evaluation. */ + snprintf(buf, sizeof buf, + "cookie=0x%llx,table=0,priority=1,actions=resubmit(,1)", + (unsigned long long)GUARDIAN_COOKIE); + if (flowset_push(fs, buf)) goto fail; + + /* TODO: per-device table-0 (dl_src->reg0) and table-1 (dl_dst->reg1) flows, + * then tables 2/3/4 policy hierarchy. Drives off cfg's group/device tables. */ + + return fs; + +fail: + guardian_flowset_free(fs); + return NULL; +} + +void guardian_flowset_free(struct guardian_flowset *fs) +{ + if (!fs) return; + for (size_t i = 0; i < fs->n; i++) + free(fs->lines[i]); + free(fs->lines); + free(fs); +} diff --git a/source/guardian/guardian_flows.h b/source/guardian/guardian_flows.h index 1a09b77..ef043b2 100755 --- a/source/guardian/guardian_flows.h +++ b/source/guardian/guardian_flows.h @@ -1,25 +1,25 @@ -/* - * Guardian policy -> OpenFlow flow translation (port of gen_flows). - * SPDX-License-Identifier: Apache-2.0 - */ -#ifndef GUARDIAN_FLOWS_H -#define GUARDIAN_FLOWS_H - -#include - -struct guardian_config; - -/* A desired flow set, as flow-mod text lines (one flow per line), matching the - * format guardian.sh emits. Kept as text for the initial port so it can be - * validated against `guardian.sh dry-run` before switching to in-memory - * ofputil_flow_mod encoding. */ -struct guardian_flowset { - char **lines; - size_t n; -}; - -/* Build the desired flow set from a parsed config. Returns NULL on error. */ -struct guardian_flowset *guardian_flows_generate(const struct guardian_config *cfg); -void guardian_flowset_free(struct guardian_flowset *fs); - -#endif /* GUARDIAN_FLOWS_H */ +/* + * Guardian policy -> OpenFlow flow translation (port of gen_flows). + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_FLOWS_H +#define GUARDIAN_FLOWS_H + +#include + +struct guardian_config; + +/* A desired flow set, as flow-mod text lines (one flow per line), matching the + * format guardian.sh emits. Kept as text for the initial port so it can be + * validated against `guardian.sh dry-run` before switching to in-memory + * ofputil_flow_mod encoding. */ +struct guardian_flowset { + char **lines; + size_t n; +}; + +/* Build the desired flow set from a parsed config. Returns NULL on error. */ +struct guardian_flowset *guardian_flows_generate(const struct guardian_config *cfg); +void guardian_flowset_free(struct guardian_flowset *fs); + +#endif /* GUARDIAN_FLOWS_H */ diff --git a/source/guardian/guardian_main.c b/source/guardian/guardian_main.c index 4e574e0..acddf20 100755 --- a/source/guardian/guardian_main.c +++ b/source/guardian/guardian_main.c @@ -1,118 +1,118 @@ -/* - * Guardian: native C port of guardian.sh. - * CLI argument parsing and command dispatch. - * - * guardian [config_file] - * - * Only apply/dry-run read the config; the others act on the live bridge. - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include - -#include "guardian.h" -#include "guardian_config.h" -#include "guardian_flows.h" -#include "guardian_ovs.h" - -static const char *const DEFAULT_CONFIG = "guardian.cfg"; - -static void usage(const char *prog) -{ - fprintf(stderr, - "Usage: %s [config_file] {apply|dry-run|clear|show|stats|diagnostics|load-test}\n" - " Config file defaults to '%s' if omitted.\n", - prog, DEFAULT_CONFIG); -} - -static enum guardian_cmd parse_cmd(const char *s) -{ - if (!strcmp(s, "apply")) return GUARDIAN_CMD_APPLY; - if (!strcmp(s, "dry-run")) return GUARDIAN_CMD_DRY_RUN; - if (!strcmp(s, "clear")) return GUARDIAN_CMD_CLEAR; - if (!strcmp(s, "show")) return GUARDIAN_CMD_SHOW; - if (!strcmp(s, "stats")) return GUARDIAN_CMD_STATS; - if (!strcmp(s, "diagnostics")) return GUARDIAN_CMD_DIAGNOSTICS; - if (!strcmp(s, "load-test")) return GUARDIAN_CMD_LOAD_TEST; - return GUARDIAN_CMD_UNKNOWN; -} - -/* Build the desired flow set from the config; caller frees. */ -static struct guardian_flowset *load_desired(const char *config) -{ - char *err = NULL; - struct guardian_config *cfg = guardian_config_load(config, &err); - if (!cfg) { - fprintf(stderr, "guardian: config error: %s\n", err ? err : "unknown"); - free(err); - return NULL; - } - struct guardian_flowset *fs = guardian_flows_generate(cfg); - guardian_config_free(cfg); - if (!fs) { - fprintf(stderr, "guardian: failed to generate flows\n"); - return NULL; - } - return fs; -} - -int main(int argc, char *argv[]) -{ - const char *config = DEFAULT_CONFIG; - const char *cmd_str; - - /* Accept "" or " ". */ - if (argc == 2) { - cmd_str = argv[1]; - } else if (argc == 3) { - config = argv[1]; - cmd_str = argv[2]; - } else { - usage(argv[0]); - return 2; - } - - enum guardian_cmd cmd = parse_cmd(cmd_str); - if (cmd == GUARDIAN_CMD_UNKNOWN) { - usage(argv[0]); - return 2; - } - - switch (cmd) { - case GUARDIAN_CMD_APPLY: { - struct guardian_flowset *fs = load_desired(config); - if (!fs) return 1; - int rc = guardian_ovs_apply(GUARDIAN_BRIDGE, fs); - guardian_flowset_free(fs); - if (rc == 0) - printf("Guardian policy applied to %s.\n", GUARDIAN_BRIDGE); - return rc ? 1 : 0; - } - case GUARDIAN_CMD_DRY_RUN: { - struct guardian_flowset *fs = load_desired(config); - if (!fs) return 1; - for (size_t i = 0; i < fs->n; i++) - puts(fs->lines[i]); - guardian_flowset_free(fs); - return 0; - } - case GUARDIAN_CMD_CLEAR: - if (guardian_ovs_clear(GUARDIAN_BRIDGE) == 0) { - printf("Guardian policy removed from %s.\n", GUARDIAN_BRIDGE); - return 0; - } - return 1; - case GUARDIAN_CMD_SHOW: - return guardian_ovs_show(GUARDIAN_BRIDGE) ? 1 : 0; - case GUARDIAN_CMD_STATS: - case GUARDIAN_CMD_DIAGNOSTICS: - case GUARDIAN_CMD_LOAD_TEST: - fprintf(stderr, "guardian: '%s' not yet implemented in the C port\n", cmd_str); - return 1; - default: - usage(argv[0]); - return 2; - } -} +/* + * Guardian: native C port of guardian.sh. + * CLI argument parsing and command dispatch. + * + * guardian [config_file] + * + * Only apply/dry-run read the config; the others act on the live bridge. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include "guardian.h" +#include "guardian_config.h" +#include "guardian_flows.h" +#include "guardian_ovs.h" + +static const char *const DEFAULT_CONFIG = "guardian.cfg"; + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [config_file] {apply|dry-run|clear|show|stats|diagnostics|load-test}\n" + " Config file defaults to '%s' if omitted.\n", + prog, DEFAULT_CONFIG); +} + +static enum guardian_cmd parse_cmd(const char *s) +{ + if (!strcmp(s, "apply")) return GUARDIAN_CMD_APPLY; + if (!strcmp(s, "dry-run")) return GUARDIAN_CMD_DRY_RUN; + if (!strcmp(s, "clear")) return GUARDIAN_CMD_CLEAR; + if (!strcmp(s, "show")) return GUARDIAN_CMD_SHOW; + if (!strcmp(s, "stats")) return GUARDIAN_CMD_STATS; + if (!strcmp(s, "diagnostics")) return GUARDIAN_CMD_DIAGNOSTICS; + if (!strcmp(s, "load-test")) return GUARDIAN_CMD_LOAD_TEST; + return GUARDIAN_CMD_UNKNOWN; +} + +/* Build the desired flow set from the config; caller frees. */ +static struct guardian_flowset *load_desired(const char *config) +{ + char *err = NULL; + struct guardian_config *cfg = guardian_config_load(config, &err); + if (!cfg) { + fprintf(stderr, "guardian: config error: %s\n", err ? err : "unknown"); + free(err); + return NULL; + } + struct guardian_flowset *fs = guardian_flows_generate(cfg); + guardian_config_free(cfg); + if (!fs) { + fprintf(stderr, "guardian: failed to generate flows\n"); + return NULL; + } + return fs; +} + +int main(int argc, char *argv[]) +{ + const char *config = DEFAULT_CONFIG; + const char *cmd_str; + + /* Accept "" or " ". */ + if (argc == 2) { + cmd_str = argv[1]; + } else if (argc == 3) { + config = argv[1]; + cmd_str = argv[2]; + } else { + usage(argv[0]); + return 2; + } + + enum guardian_cmd cmd = parse_cmd(cmd_str); + if (cmd == GUARDIAN_CMD_UNKNOWN) { + usage(argv[0]); + return 2; + } + + switch (cmd) { + case GUARDIAN_CMD_APPLY: { + struct guardian_flowset *fs = load_desired(config); + if (!fs) return 1; + int rc = guardian_ovs_apply(GUARDIAN_BRIDGE, fs); + guardian_flowset_free(fs); + if (rc == 0) + printf("Guardian policy applied to %s.\n", GUARDIAN_BRIDGE); + return rc ? 1 : 0; + } + case GUARDIAN_CMD_DRY_RUN: { + struct guardian_flowset *fs = load_desired(config); + if (!fs) return 1; + for (size_t i = 0; i < fs->n; i++) + puts(fs->lines[i]); + guardian_flowset_free(fs); + return 0; + } + case GUARDIAN_CMD_CLEAR: + if (guardian_ovs_clear(GUARDIAN_BRIDGE) == 0) { + printf("Guardian policy removed from %s.\n", GUARDIAN_BRIDGE); + return 0; + } + return 1; + case GUARDIAN_CMD_SHOW: + return guardian_ovs_show(GUARDIAN_BRIDGE) ? 1 : 0; + case GUARDIAN_CMD_STATS: + case GUARDIAN_CMD_DIAGNOSTICS: + case GUARDIAN_CMD_LOAD_TEST: + fprintf(stderr, "guardian: '%s' not yet implemented in the C port\n", cmd_str); + return 1; + default: + usage(argv[0]); + return 2; + } +} diff --git a/source/guardian/guardian_ovs.c b/source/guardian/guardian_ovs.c index 0a11b4a..ac5f6c6 100755 --- a/source/guardian/guardian_ovs.c +++ b/source/guardian/guardian_ovs.c @@ -1,57 +1,184 @@ -/* - * Guardian OVS I/O. - * - * Initial scaffold uses a single `ovs-ofctl` invocation (Option B in - * PORTING.md): the whole desired flow set is streamed once via stdin, removing - * the per-line forking of guardian.sh. This keeps the port buildable without - * the OVS staticdev sysroot. The follow-up step swaps this for libopenvswitch - * (vconn_open + vconn_dump_flows delta + ofputil_encode_flow_mod) per PORTING.md - * section 2 Option A. - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include "guardian_ovs.h" -#include "guardian.h" - -#include -#include -#include - -int guardian_ovs_apply(const char *bridge, const struct guardian_flowset *desired) -{ - /* Full-table replace for the initial port: clear our cookie, then add the - * desired set in one add-flows call. Delta comes with the Option A rewrite. */ - if (guardian_ovs_clear(bridge) != 0) - return -1; - - char cmd[256]; - snprintf(cmd, sizeof cmd, "ovs-ofctl add-flows %s -", bridge); - FILE *p = popen(cmd, "w"); - if (!p) { - perror("guardian: popen(ovs-ofctl add-flows)"); - return -1; - } - for (size_t i = 0; i < desired->n; i++) - fprintf(p, "%s\n", desired->lines[i]); - - int rc = pclose(p); - return rc == 0 ? 0 : -1; -} - -int guardian_ovs_clear(const char *bridge) -{ - char cmd[256]; - snprintf(cmd, sizeof cmd, - "ovs-ofctl del-flows %s \"cookie=0x%llx/-1\"", - bridge, (unsigned long long)GUARDIAN_COOKIE); - return system(cmd) == 0 ? 0 : -1; -} - -int guardian_ovs_show(const char *bridge) -{ - char cmd[256]; - snprintf(cmd, sizeof cmd, - "ovs-ofctl dump-flows %s | grep -i 0x%llx || echo 'No Guardian flows.'", - bridge, (unsigned long long)GUARDIAN_COOKIE); - return system(cmd) == 0 ? 0 : -1; -} +/* + * Guardian OVS I/O via libopenvswitch (PORTING.md Option A). + * + * Opens a management vconn to the bridge and drives flow changes over + * OpenFlow directly -- no ovs-ofctl fork. apply/clear use flow-mods parsed + * with parse_ofp_flow_mod_str(); show uses vconn_dump_flows() filtered by our + * cookie. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "guardian_ovs.h" +#include "guardian.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* OVS runtime dir holding the .mgmt punix socket. Override at build. */ +#ifndef GUARDIAN_OVS_RUNDIR +#define GUARDIAN_OVS_RUNDIR "/var/run/openvswitch" +#endif + +/* Endian-safe host uint64 -> ovs_be64 (network byte order). */ +static ovs_be64 host_to_be64(uint64_t v) +{ + uint8_t b[8]; + for (int i = 0; i < 8; i++) + b[7 - i] = (uint8_t)(v >> (8 * i)); + ovs_be64 r; + memcpy(&r, b, sizeof r); + return r; +} + +/* Open a management vconn to the bridge and negotiate a protocol. */ +static int open_bridge(const char *bridge, struct vconn **vconnp, + enum ofputil_protocol *protocolp) +{ + char name[256]; + snprintf(name, sizeof name, "unix:%s/%s.mgmt", GUARDIAN_OVS_RUNDIR, bridge); + + int error = vconn_open(name, OFPUTIL_DEFAULT_VERSIONS, 0 /*dscp*/, vconnp); + if (error) { + fprintf(stderr, "guardian: vconn_open(%s) failed: %s\n", + name, ovs_strerror(error)); + return -1; + } + error = vconn_connect_block(*vconnp, -1); + if (error) { + fprintf(stderr, "guardian: connect to %s failed: %s\n", + name, ovs_strerror(error)); + vconn_close(*vconnp); + return -1; + } + *protocolp = ofputil_protocol_from_ofp_version(vconn_get_version(*vconnp)); + if (!*protocolp) { + fprintf(stderr, "guardian: unsupported OpenFlow version on %s\n", bridge); + vconn_close(*vconnp); + return -1; + } + return 0; +} + +/* Parse one flow-mod string and send it over the vconn. */ +static int send_flow_mod(struct vconn *vconn, enum ofputil_protocol protocol, + const char *str, int command) +{ + struct ofputil_flow_mod fm; + enum ofputil_protocol usable; + char *err = parse_ofp_flow_mod_str(&fm, str, NULL, NULL, command, &usable); + if (err) { + fprintf(stderr, "guardian: bad flow '%s': %s\n", str, err); + free(err); + return -1; + } + + struct ofpbuf *msg = ofputil_encode_flow_mod(&fm, protocol); + struct ofpbuf *reply = NULL; + int error = vconn_transact_noreply(vconn, msg, &reply); + if (reply) { + ofpbuf_delete(reply); + } + free(fm.ofpacts); + minimatch_destroy(&fm.match); + + if (error) { + fprintf(stderr, "guardian: send flow failed: %s\n", ovs_strerror(error)); + return -1; + } + return 0; +} + +/* "cookie=0x9110/-1" match string selecting all Guardian flows. */ +static void cookie_match(char *buf, size_t len) +{ + snprintf(buf, len, "cookie=0x%llx/-1", (unsigned long long)GUARDIAN_COOKIE); +} + +int guardian_ovs_apply(const char *bridge, const struct guardian_flowset *desired) +{ + struct vconn *vconn; + enum ofputil_protocol protocol; + if (open_bridge(bridge, &vconn, &protocol)) + return -1; + + /* Full-table replace for now: delete our cookie, then add the desired set. + * TODO: in-memory delta via vconn_dump_flows() (add/del only what changed). */ + char del[64]; + cookie_match(del, sizeof del); + int rc = send_flow_mod(vconn, protocol, del, OFPFC_DELETE); + + for (size_t i = 0; rc == 0 && i < desired->n; i++) + rc = send_flow_mod(vconn, protocol, desired->lines[i], OFPFC_ADD); + + vconn_close(vconn); + return rc; +} + +int guardian_ovs_clear(const char *bridge) +{ + struct vconn *vconn; + enum ofputil_protocol protocol; + if (open_bridge(bridge, &vconn, &protocol)) + return -1; + + char del[64]; + cookie_match(del, sizeof del); + int rc = send_flow_mod(vconn, protocol, del, OFPFC_DELETE); + + vconn_close(vconn); + return rc; +} + +int guardian_ovs_show(const char *bridge) +{ + struct vconn *vconn; + enum ofputil_protocol protocol; + if (open_bridge(bridge, &vconn, &protocol)) + return -1; + + struct ofputil_flow_stats_request fsr; + memset(&fsr, 0, sizeof fsr); + fsr.aggregate = false; + match_init_catchall(&fsr.match); + fsr.cookie = host_to_be64(GUARDIAN_COOKIE); + fsr.cookie_mask = OVS_BE64_MAX; + fsr.out_port = OFPP_ANY; + fsr.out_group = OFPG_ANY; + fsr.table_id = OFPTT_ALL; + + struct ofputil_flow_stats *fses = NULL; + size_t n = 0; + int error = vconn_dump_flows(vconn, &fsr, protocol, &fses, &n); + if (error) { + fprintf(stderr, "guardian: dump-flows failed: %s\n", ovs_strerror(error)); + vconn_close(vconn); + return -1; + } + + if (n == 0) { + printf("No Guardian flows.\n"); + } else { + for (size_t i = 0; i < n; i++) { + struct ds ds = DS_EMPTY_INITIALIZER; + ofputil_flow_stats_format(&ds, &fses[i], NULL, NULL, true); + printf("%s\n", ds_cstr(&ds)); + ds_destroy(&ds); + } + } + + free(fses); + vconn_close(vconn); + return 0; +} diff --git a/source/guardian/guardian_ovs.h b/source/guardian/guardian_ovs.h index 9258917..deceaf4 100755 --- a/source/guardian/guardian_ovs.h +++ b/source/guardian/guardian_ovs.h @@ -1,21 +1,21 @@ -/* - * Guardian OVS I/O: connect to the bridge, dump live flows, apply add/del. - * SPDX-License-Identifier: Apache-2.0 - */ -#ifndef GUARDIAN_OVS_H -#define GUARDIAN_OVS_H - -#include "guardian_flows.h" - -/* Apply the desired flow set to the bridge using a delta against the live - * flows (cookie GUARDIAN_COOKIE): only changed flows are added/deleted. - * Returns 0 on success. */ -int guardian_ovs_apply(const char *bridge, const struct guardian_flowset *desired); - -/* Remove all Guardian flows (cookie GUARDIAN_COOKIE) from the bridge. */ -int guardian_ovs_clear(const char *bridge); - -/* Print active Guardian flows (equivalent to guardian.sh show). */ -int guardian_ovs_show(const char *bridge); - -#endif /* GUARDIAN_OVS_H */ +/* + * Guardian OVS I/O: connect to the bridge, dump live flows, apply add/del. + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_OVS_H +#define GUARDIAN_OVS_H + +#include "guardian_flows.h" + +/* Apply the desired flow set to the bridge using a delta against the live + * flows (cookie GUARDIAN_COOKIE): only changed flows are added/deleted. + * Returns 0 on success. */ +int guardian_ovs_apply(const char *bridge, const struct guardian_flowset *desired); + +/* Remove all Guardian flows (cookie GUARDIAN_COOKIE) from the bridge. */ +int guardian_ovs_clear(const char *bridge); + +/* Print active Guardian flows (equivalent to guardian.sh show). */ +int guardian_ovs_show(const char *bridge); + +#endif /* GUARDIAN_OVS_H */ From d1604b7294fee4d0e3c3cf62eeb966b865f83fa7 Mon Sep 17 00:00:00 2001 From: Rajkamal CV Date: Wed, 9 Sep 2026 12:06:20 +0530 Subject: [PATCH 3/4] RDKB-66541 : Use strerror instead of ovs_strerror --- source/guardian/guardian_ovs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/guardian/guardian_ovs.c b/source/guardian/guardian_ovs.c index ac5f6c6..a2754a4 100755 --- a/source/guardian/guardian_ovs.c +++ b/source/guardian/guardian_ovs.c @@ -52,13 +52,13 @@ static int open_bridge(const char *bridge, struct vconn **vconnp, int error = vconn_open(name, OFPUTIL_DEFAULT_VERSIONS, 0 /*dscp*/, vconnp); if (error) { fprintf(stderr, "guardian: vconn_open(%s) failed: %s\n", - name, ovs_strerror(error)); + name, strerror(error)); return -1; } error = vconn_connect_block(*vconnp, -1); if (error) { fprintf(stderr, "guardian: connect to %s failed: %s\n", - name, ovs_strerror(error)); + name, strerror(error)); vconn_close(*vconnp); return -1; } @@ -94,7 +94,7 @@ static int send_flow_mod(struct vconn *vconn, enum ofputil_protocol protocol, minimatch_destroy(&fm.match); if (error) { - fprintf(stderr, "guardian: send flow failed: %s\n", ovs_strerror(error)); + fprintf(stderr, "guardian: send flow failed: %s\n", strerror(error)); return -1; } return 0; @@ -162,7 +162,7 @@ int guardian_ovs_show(const char *bridge) size_t n = 0; int error = vconn_dump_flows(vconn, &fsr, protocol, &fses, &n); if (error) { - fprintf(stderr, "guardian: dump-flows failed: %s\n", ovs_strerror(error)); + fprintf(stderr, "guardian: dump-flows failed: %s\n", strerror(error)); vconn_close(vconn); return -1; } From 27bdc1da6684a4e15da5bbda3b0537fe02a576c5 Mon Sep 17 00:00:00 2001 From: Rajkamal CV Date: Wed, 9 Sep 2026 15:37:54 +0530 Subject: [PATCH 4/4] RDKB-66541 : Generate actual OVS table rules (complete port of guardian.sh) --- source/guardian/guardian_config.c | 268 +++++++++++++++-- source/guardian/guardian_config.h | 59 +++- source/guardian/guardian_flows.c | 471 ++++++++++++++++++++++++++++-- 3 files changed, 751 insertions(+), 47 deletions(-) diff --git a/source/guardian/guardian_config.c b/source/guardian/guardian_config.c index 3125e61..91d8169 100755 --- a/source/guardian/guardian_config.c +++ b/source/guardian/guardian_config.c @@ -1,10 +1,10 @@ /* - * Guardian config parsing + delta computation. + * Guardian config parsing. * - * Initial port scaffold: parses the INI-style guardian.cfg into in-memory - * tables (groups, devices, services, macros, policies, defaults). The flow - * generation (guardian_flows.c) consumes these tables instead of forking awk - * per line as guardian.sh did. + * Parses the INI-style guardian.cfg into in-memory tables (groups, devices, + * services, macros, policies, defaults). guardian_flows.c consumes these + * tables via O(1)/O(n) in-process lookups instead of forking awk per line as + * guardian.sh did. * * SPDX-License-Identifier: Apache-2.0 */ @@ -16,28 +16,41 @@ #include #include -/* TODO: replace fixed caps with dynamic arrays once parsing is fleshed out. */ #define MAX_GROUPS 64 #define MAX_DEVICES 512 -#define MAX_NAME 64 +#define MAX_SERVICES 128 +#define MAX_MACROS 64 +#define MAX_POLICY 256 -struct group_entry { - char name[MAX_NAME]; - uint32_t id; -}; - -struct device_entry { - char mac[MAX_NAME]; - char group[MAX_NAME]; -}; +struct group_entry { char name[GUARDIAN_NAME_MAX]; uint32_t id; }; +struct device_entry { char mac[GUARDIAN_NAME_MAX]; char group[GUARDIAN_NAME_MAX]; }; +struct service_entry { char name[GUARDIAN_NAME_MAX]; char proto[16]; char port[16]; }; +struct macro_entry { char name[GUARDIAN_NAME_MAX]; char expansion[256]; }; +struct group_policy_row { char src[GUARDIAN_NAME_MAX], dst[GUARDIAN_NAME_MAX], svc[GUARDIAN_NAME_MAX], action[16]; }; +struct intra_policy_row { char grp[GUARDIAN_NAME_MAX], svc[GUARDIAN_NAME_MAX], action[16]; }; +struct device_policy_row { char src[GUARDIAN_NAME_MAX], dst_mac[GUARDIAN_NAME_MAX], svc[GUARDIAN_NAME_MAX], action[16]; }; struct guardian_config { struct group_entry groups[MAX_GROUPS]; size_t n_groups; struct device_entry devices[MAX_DEVICES]; size_t n_devices; - bool stateful; - /* TODO: services, service_macros, group/intra/device policy, other defaults. */ + struct service_entry services[MAX_SERVICES]; + size_t n_services; + struct macro_entry macros[MAX_MACROS]; + size_t n_macros; + struct group_policy_row group_policy[MAX_POLICY]; + size_t n_group_policy; + struct intra_policy_row intra_policy[MAX_POLICY]; + size_t n_intra_policy; + struct device_policy_row device_policy[MAX_POLICY]; + size_t n_device_policy; + + char src_groups[MAX_GROUPS][GUARDIAN_NAME_MAX]; + size_t n_src_groups; + + bool stateful; + bool drop_invalid; }; /* Strip inline " # comment", leading/trailing whitespace; returns trimmed @@ -58,6 +71,15 @@ static char *trim_line(char *buf) return h; } +static void remember_src_group(struct guardian_config *cfg, const char *grp) +{ + for (size_t i = 0; i < cfg->n_src_groups; i++) + if (!strcasecmp(cfg->src_groups[i], grp)) + return; + if (cfg->n_src_groups < MAX_GROUPS) + snprintf(cfg->src_groups[cfg->n_src_groups++], GUARDIAN_NAME_MAX, "%s", grp); +} + struct guardian_config *guardian_config_load(const char *path, char **err) { FILE *fp = fopen(path, "r"); @@ -74,7 +96,7 @@ struct guardian_config *guardian_config_load(const char *path, char **err) if (!cfg) { fclose(fp); if (err) *err = strdup("out of memory"); return NULL; } char line[512]; - char section[MAX_NAME] = ""; + char section[GUARDIAN_NAME_MAX] = ""; while (fgets(line, sizeof line, fp)) { char *s = trim_line(line); @@ -92,27 +114,75 @@ struct guardian_config *guardian_config_load(const char *path, char **err) } if (!strcasecmp(section, "groups")) { - char name[MAX_NAME]; unsigned long id; + char name[GUARDIAN_NAME_MAX]; unsigned long id; if (sscanf(s, "%63s %lu", name, &id) == 2 && cfg->n_groups < MAX_GROUPS) { struct group_entry *g = &cfg->groups[cfg->n_groups++]; snprintf(g->name, sizeof g->name, "%s", name); g->id = (uint32_t)id; } } else if (!strcasecmp(section, "devices")) { - char mac[MAX_NAME], grp[MAX_NAME]; + char mac[GUARDIAN_NAME_MAX], grp[GUARDIAN_NAME_MAX]; if (sscanf(s, "%63s %63s", mac, grp) == 2 && cfg->n_devices < MAX_DEVICES) { struct device_entry *d = &cfg->devices[cfg->n_devices++]; snprintf(d->mac, sizeof d->mac, "%s", mac); snprintf(d->group, sizeof d->group, "%s", grp); + remember_src_group(cfg, grp); + } + } else if (!strcasecmp(section, "services")) { + char name[GUARDIAN_NAME_MAX], proto[16], port[16]; + if (sscanf(s, "%63s %15s %15s", name, proto, port) == 3 && + cfg->n_services < MAX_SERVICES) { + struct service_entry *e = &cfg->services[cfg->n_services++]; + snprintf(e->name, sizeof e->name, "%s", name); + snprintf(e->proto, sizeof e->proto, "%s", proto); + snprintf(e->port, sizeof e->port, "%s", port); + } + } else if (!strcasecmp(section, "service_macros")) { + char name[GUARDIAN_NAME_MAX], expansion[256]; + if (sscanf(s, "%63s %255s", name, expansion) == 2 && + cfg->n_macros < MAX_MACROS) { + struct macro_entry *m = &cfg->macros[cfg->n_macros++]; + snprintf(m->name, sizeof m->name, "%s", name); + snprintf(m->expansion, sizeof m->expansion, "%s", expansion); + } + } else if (!strcasecmp(section, "group_policy")) { + char src[GUARDIAN_NAME_MAX], dst[GUARDIAN_NAME_MAX], svc[GUARDIAN_NAME_MAX], act[16]; + if (sscanf(s, "%63s %63s %63s %15s", src, dst, svc, act) == 4 && + cfg->n_group_policy < MAX_POLICY) { + struct group_policy_row *r = &cfg->group_policy[cfg->n_group_policy++]; + snprintf(r->src, sizeof r->src, "%s", src); + snprintf(r->dst, sizeof r->dst, "%s", dst); + snprintf(r->svc, sizeof r->svc, "%s", svc); + snprintf(r->action, sizeof r->action, "%s", act); + } + } else if (!strcasecmp(section, "intra_group_policy")) { + char grp[GUARDIAN_NAME_MAX], svc[GUARDIAN_NAME_MAX], act[16]; + if (sscanf(s, "%63s %63s %15s", grp, svc, act) == 3 && + cfg->n_intra_policy < MAX_POLICY) { + struct intra_policy_row *r = &cfg->intra_policy[cfg->n_intra_policy++]; + snprintf(r->grp, sizeof r->grp, "%s", grp); + snprintf(r->svc, sizeof r->svc, "%s", svc); + snprintf(r->action, sizeof r->action, "%s", act); + } + } else if (!strcasecmp(section, "device_policy")) { + char src[GUARDIAN_NAME_MAX], dst[GUARDIAN_NAME_MAX], svc[GUARDIAN_NAME_MAX], act[16]; + if (sscanf(s, "%63s %63s %63s %15s", src, dst, svc, act) == 4 && + cfg->n_device_policy < MAX_POLICY) { + struct device_policy_row *r = &cfg->device_policy[cfg->n_device_policy++]; + snprintf(r->src, sizeof r->src, "%s", src); + snprintf(r->dst_mac, sizeof r->dst_mac, "%s", dst); + snprintf(r->svc, sizeof r->svc, "%s", svc); + snprintf(r->action, sizeof r->action, "%s", act); } } else if (!strcasecmp(section, "defaults")) { - char key[MAX_NAME], val[MAX_NAME]; - if (sscanf(s, "%63s %63s", key, val) == 2 && - !strcasecmp(key, "stateful")) { - cfg->stateful = !strcasecmp(val, "true"); + char key[GUARDIAN_NAME_MAX], val[GUARDIAN_NAME_MAX]; + if (sscanf(s, "%63s %63s", key, val) == 2) { + if (!strcasecmp(key, "stateful")) + cfg->stateful = !strcasecmp(val, "true"); + else if (!strcasecmp(key, "drop_invalid")) + cfg->drop_invalid = !strcasecmp(val, "true"); } } - /* TODO: services, service_macros, *_policy sections. */ } fclose(fp); @@ -137,3 +207,149 @@ bool guardian_config_stateful(const struct guardian_config *cfg) { return cfg->stateful; } + +bool guardian_config_drop_invalid(const struct guardian_config *cfg) +{ + return cfg->drop_invalid; +} + +size_t guardian_config_n_devices(const struct guardian_config *cfg) +{ + return cfg->n_devices; +} + +void guardian_config_device(const struct guardian_config *cfg, size_t i, + const char **mac, const char **group) +{ + *mac = cfg->devices[i].mac; + *group = cfg->devices[i].group; +} + +const char *guardian_config_group_of_mac(const struct guardian_config *cfg, + const char *mac) +{ + for (size_t i = 0; i < cfg->n_devices; i++) + if (!strcasecmp(cfg->devices[i].mac, mac)) + return cfg->devices[i].group; + return NULL; +} + +size_t guardian_config_n_src_groups(const struct guardian_config *cfg) +{ + return cfg->n_src_groups; +} + +const char *guardian_config_src_group(const struct guardian_config *cfg, size_t i) +{ + return cfg->src_groups[i]; +} + +size_t guardian_config_n_group_policy(const struct guardian_config *cfg) +{ + return cfg->n_group_policy; +} + +void guardian_config_group_policy(const struct guardian_config *cfg, size_t i, + const char **src, const char **dst, + const char **svc, const char **action) +{ + *src = cfg->group_policy[i].src; + *dst = cfg->group_policy[i].dst; + *svc = cfg->group_policy[i].svc; + *action = cfg->group_policy[i].action; +} + +size_t guardian_config_n_intra_policy(const struct guardian_config *cfg) +{ + return cfg->n_intra_policy; +} + +void guardian_config_intra_policy(const struct guardian_config *cfg, size_t i, + const char **grp, const char **svc, + const char **action) +{ + *grp = cfg->intra_policy[i].grp; + *svc = cfg->intra_policy[i].svc; + *action = cfg->intra_policy[i].action; +} + +size_t guardian_config_n_device_policy(const struct guardian_config *cfg) +{ + return cfg->n_device_policy; +} + +void guardian_config_device_policy(const struct guardian_config *cfg, size_t i, + const char **src, const char **dst_mac, + const char **svc, const char **action) +{ + *src = cfg->device_policy[i].src; + *dst_mac = cfg->device_policy[i].dst_mac; + *svc = cfg->device_policy[i].svc; + *action = cfg->device_policy[i].action; +} + +static size_t expand_basic(const struct guardian_config *cfg, const char *svc, + struct guardian_service *out, size_t max) +{ + for (size_t i = 0; i < cfg->n_services && max > 0; i++) { + if (!strcasecmp(cfg->services[i].name, svc)) { + snprintf(out->proto, sizeof out->proto, "%s", cfg->services[i].proto); + snprintf(out->port, sizeof out->port, "%s", cfg->services[i].port); + return 1; + } + } + return 0; +} + +size_t guardian_config_expand_service(const struct guardian_config *cfg, + const char *svc, + struct guardian_service *out, size_t max) +{ + for (size_t i = 0; i < cfg->n_macros; i++) { + if (strcasecmp(cfg->macros[i].name, svc)) continue; + + /* Macro: comma-separated list of basic service names. */ + char buf[256]; + snprintf(buf, sizeof buf, "%s", cfg->macros[i].expansion); + size_t n = 0; + char *save = NULL; + for (char *tok = strtok_r(buf, ",", &save); tok && n < max; + tok = strtok_r(NULL, ",", &save)) { + n += expand_basic(cfg, tok, &out[n], max - n); + } + return n; + } + /* Not a macro: look up as a basic service. */ + return expand_basic(cfg, svc, out, max); +} + +bool guardian_config_policy_is_drop(const struct guardian_config *cfg, + const char *src_grp, const char *dst_grp) +{ + for (size_t i = 0; i < cfg->n_group_policy; i++) { + const struct group_policy_row *r = &cfg->group_policy[i]; + if (!strcasecmp(r->src, src_grp) && !strcasecmp(r->dst, dst_grp) && + !strcasecmp(r->svc, "any") && !strcasecmp(r->action, "drop")) + return true; + } + return false; +} + +bool guardian_config_service_allows_port(const struct guardian_config *cfg, + const char *src_grp, const char *dst_grp, + const char *proto, const char *port) +{ + for (size_t i = 0; i < cfg->n_group_policy; i++) { + const struct group_policy_row *r = &cfg->group_policy[i]; + if (strcasecmp(r->src, src_grp) || strcasecmp(r->dst, dst_grp) || + strcasecmp(r->action, "allow") || !strcasecmp(r->svc, "any")) + continue; + + struct guardian_service svcs[16]; + size_t n = guardian_config_expand_service(cfg, r->svc, svcs, 16); + for (size_t j = 0; j < n; j++) + if (!strcmp(svcs[j].proto, proto) && !strcmp(svcs[j].port, port)) + return true; + } + return false; +} diff --git a/source/guardian/guardian_config.h b/source/guardian/guardian_config.h index 8dc3163..14c0e47 100755 --- a/source/guardian/guardian_config.h +++ b/source/guardian/guardian_config.h @@ -1,5 +1,5 @@ /* - * Guardian config parsing + delta computation. + * Guardian config parsing + accessors. * SPDX-License-Identifier: Apache-2.0 */ #ifndef GUARDIAN_CONFIG_H @@ -9,6 +9,8 @@ #include #include +#define GUARDIAN_NAME_MAX 64 + /* Parsed configuration. Populated from the INI-style guardian.cfg. */ struct guardian_config; @@ -21,7 +23,60 @@ void guardian_config_free(struct guardian_config *cfg); uint32_t guardian_config_group_id(const struct guardian_config *cfg, const char *name); -/* Whether stateful (conntrack) mode is enabled in [defaults]. */ +/* [defaults] */ bool guardian_config_stateful(const struct guardian_config *cfg); +bool guardian_config_drop_invalid(const struct guardian_config *cfg); + +/* [devices]: mac -> group name. */ +size_t guardian_config_n_devices(const struct guardian_config *cfg); +void guardian_config_device(const struct guardian_config *cfg, size_t i, + const char **mac, const char **group); + +/* Return the configured group of a MAC, or NULL if unmanaged. */ +const char *guardian_config_group_of_mac(const struct guardian_config *cfg, + const char *mac); + +/* Unique source groups seen in [devices], for table-3 multicast generation. */ +size_t guardian_config_n_src_groups(const struct guardian_config *cfg); +const char *guardian_config_src_group(const struct guardian_config *cfg, size_t i); + +/* [group_policy]: SRC_GROUP DST_GROUP SERVICE ACTION. */ +size_t guardian_config_n_group_policy(const struct guardian_config *cfg); +void guardian_config_group_policy(const struct guardian_config *cfg, size_t i, + const char **src, const char **dst, + const char **svc, const char **action); + +/* [intra_group_policy]: GROUP SERVICE ACTION. */ +size_t guardian_config_n_intra_policy(const struct guardian_config *cfg); +void guardian_config_intra_policy(const struct guardian_config *cfg, size_t i, + const char **grp, const char **svc, + const char **action); + +/* [device_policy]: SRC_GROUP DST_MAC SERVICE ACTION. */ +size_t guardian_config_n_device_policy(const struct guardian_config *cfg); +void guardian_config_device_policy(const struct guardian_config *cfg, size_t i, + const char **src, const char **dst_mac, + const char **svc, const char **action); + +/* A single protocol/port pair a service expands to. */ +struct guardian_service { + char proto[16]; + char port[16]; +}; + +/* Expand a service name (handles [service_macros]) to one or more basic + * proto/port pairs. Returns the number written into out (capped at max). */ +size_t guardian_config_expand_service(const struct guardian_config *cfg, + const char *svc, + struct guardian_service *out, size_t max); + +/* True if an explicit "src dst ANY DROP" group_policy row exists. */ +bool guardian_config_policy_is_drop(const struct guardian_config *cfg, + const char *src_grp, const char *dst_grp); + +/* True if group_policy explicitly ALLOWs proto/port from src to dst. */ +bool guardian_config_service_allows_port(const struct guardian_config *cfg, + const char *src_grp, const char *dst_grp, + const char *proto, const char *port); #endif /* GUARDIAN_CONFIG_H */ diff --git a/source/guardian/guardian_flows.c b/source/guardian/guardian_flows.c index cab1c83..65a0aac 100755 --- a/source/guardian/guardian_flows.c +++ b/source/guardian/guardian_flows.c @@ -1,10 +1,11 @@ /* * Guardian policy -> OpenFlow flow translation (port of gen_flows). * - * Initial scaffold: emits the table-0/table-1 source/dest classification flows - * from the parsed config. Remaining tables (policy hierarchy, stateful ct path, - * multicast delivery) are ported incrementally and validated against - * `guardian.sh dry-run`. + * Emits the same flow-mod text lines guardian.sh's gen_flows() produces: + * table 0: dl_src -> reg0 classification + * table 1: dl_dst -> reg1 classification; multicast/broadcast -> table 3 + * table 2/4: priority hierarchy 160/150/140/110/105/100 (+ stateful ct path) + * table 3: group-scoped multicast delivery (mc2uc), via live FDB snapshot * * SPDX-License-Identifier: Apache-2.0 */ @@ -15,39 +16,471 @@ #include #include #include +#include +#include +#include +#include +#include -/* Access to the parsed tables. Kept internal to config.c today; the flow - * builder currently drives generation through the public accessors and a small - * device iterator added here as the port grows. For now we expose just enough - * via accessor calls. */ +/* libopenvswitch exports these (see utilities/ovs-appctl.c), but their headers + * (daemon.h/dirs.h/unixctl.h/jsonrpc.h) are OVS-internal and not shipped in the + * dev sysroot, so declare the few prototypes we use here. */ +struct jsonrpc; +extern const char *ovs_rundir(void); +extern pid_t read_pidfile(const char *name); +extern int unixctl_client_create(const char *path, struct jsonrpc **client); +extern int unixctl_client_transact(struct jsonrpc *client, const char *command, + int argc, char *argv[], + char **result, char **error); +extern void jsonrpc_close(struct jsonrpc *); -static int flowset_push(struct guardian_flowset *fs, const char *line) +#define MAX_FDB 1024 + +struct fdb_entry { + char port[16]; + char mac[GUARDIAN_NAME_MAX]; +}; + +static int flowset_push(struct guardian_flowset *fs, const char *fmt, ...) { + char buf[512]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + char **grown = realloc(fs->lines, (fs->n + 1) * sizeof *fs->lines); if (!grown) return -1; fs->lines = grown; - fs->lines[fs->n] = strdup(line); + fs->lines[fs->n] = strdup(buf); if (!fs->lines[fs->n]) return -1; fs->n++; return 0; } +/* Resolve a group name to its numeric id, falling back to the raw name (as + * guardian.sh does) if it isn't found in [groups]. */ +static const char *group_id_str(const struct guardian_config *cfg, + const char *name, char *buf, size_t len) +{ + uint32_t id = guardian_config_group_id(cfg, name); + if (id == 0) { + snprintf(buf, len, "%s", name); + return buf; + } + snprintf(buf, len, "%u", id); + return buf; +} + +static bool is_allow(const char *action) +{ + return !strcasecmp(action, "allow"); +} + +static bool is_any(const char *svc) +{ + return !strcasecmp(svc, "any"); +} + +/* Emit table-0/1 classification flows for every configured device. */ +static int gen_device_tables(const struct guardian_config *cfg, struct guardian_flowset *fs) +{ + size_t n = guardian_config_n_devices(cfg); + for (size_t i = 0; i < n; i++) { + const char *mac, *grp; + guardian_config_device(cfg, i, &mac, &grp); + char idbuf[16]; + const char *id = group_id_str(cfg, grp, idbuf, sizeof idbuf); + if (flowset_push(fs, + "cookie=0x%llx,table=0,priority=100,dl_src=%s,actions=load:%s->NXM_NX_REG0[],resubmit(,1)", + (unsigned long long)GUARDIAN_COOKIE, mac, id)) + return -1; + } + if (flowset_push(fs, "cookie=0x%llx,table=0,priority=1,actions=resubmit(,1)", + (unsigned long long)GUARDIAN_COOKIE)) + return -1; + + for (size_t i = 0; i < n; i++) { + const char *mac, *grp; + guardian_config_device(cfg, i, &mac, &grp); + char idbuf[16]; + const char *id = group_id_str(cfg, grp, idbuf, sizeof idbuf); + if (flowset_push(fs, + "cookie=0x%llx,table=1,priority=100,dl_dst=%s,actions=load:%s->NXM_NX_REG1[],resubmit(,2)", + (unsigned long long)GUARDIAN_COOKIE, mac, id)) + return -1; + } + if (flowset_push(fs, + "cookie=0x%llx,table=1,priority=200,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00,actions=resubmit(,3)", + (unsigned long long)GUARDIAN_COOKIE)) + return -1; + if (flowset_push(fs, "cookie=0x%llx,table=1,priority=0,actions=resubmit(,2)", + (unsigned long long)GUARDIAN_COOKIE)) + return -1; + return 0; +} + +/* Emit priority-160 device-specific policy (table 2/4). */ +static int gen_device_policy(const struct guardian_config *cfg, struct guardian_flowset *fs, + int pt, const char *newm, const char *allowa) +{ + unsigned long long cookie = (unsigned long long)GUARDIAN_COOKIE; + size_t n = guardian_config_n_device_policy(cfg); + for (size_t i = 0; i < n; i++) { + const char *src, *dst_mac, *svc, *action; + guardian_config_device_policy(cfg, i, &src, &dst_mac, &svc, &action); + char idbuf[16]; + const char *src_id = group_id_str(cfg, src, idbuf, sizeof idbuf); + + if (is_any(svc)) { + if (is_allow(action)) { + if (pt == 4) { + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=160,ct_state=+new,ip,reg0=%s,dl_dst=%s,actions=%s", + cookie, src_id, dst_mac, allowa)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=160,ct_state=+new,ipv6,reg0=%s,dl_dst=%s,actions=%s", + cookie, src_id, dst_mac, allowa)) return -1; + } else { + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=160,reg0=%s,dl_dst=%s,actions=NORMAL", + cookie, src_id, dst_mac)) return -1; + } + } else { + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=160,reg0=%s,dl_dst=%s,actions=drop", + cookie, src_id, dst_mac)) return -1; + if (pt == 4) { + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=160,ct_state=+new,ip,reg0=%s,dl_dst=%s,actions=drop", + cookie, src_id, dst_mac)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=160,ct_state=+new,ipv6,reg0=%s,dl_dst=%s,actions=drop", + cookie, src_id, dst_mac)) return -1; + } + } + continue; + } + + struct guardian_service svcs[16]; + size_t ns = guardian_config_expand_service(cfg, svc, svcs, 16); + for (size_t j = 0; j < ns; j++) { + const char *proto = svcs[j].proto, *port = svcs[j].port; + const char *pf = !strcmp(proto, "sctp") ? "sctp_dst" : "tp_dst"; + const char *act = is_allow(action) ? allowa : "drop"; + if (flowset_push(fs, "cookie=0x%llx,table=%d,priority=160%s,ip,%s,dl_dst=%s,%s=%s,reg0=%s,actions=%s", + cookie, pt, newm, proto, dst_mac, pf, port, src_id, act)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=%d,priority=160%s,%s6,dl_dst=%s,%s=%s,reg0=%s,actions=%s", + cookie, pt, newm, proto, dst_mac, pf, port, src_id, act)) return -1; + } + } + return 0; +} + +/* Emit priority-150 group service policy (table 2/4). */ +static int gen_group_service_policy(const struct guardian_config *cfg, struct guardian_flowset *fs, + int pt, const char *newm, const char *allowa) +{ + unsigned long long cookie = (unsigned long long)GUARDIAN_COOKIE; + size_t n = guardian_config_n_group_policy(cfg); + for (size_t i = 0; i < n; i++) { + const char *src, *dst, *svc, *action; + guardian_config_group_policy(cfg, i, &src, &dst, &svc, &action); + if (is_any(svc)) continue; /* handled at priority 100 */ + + char sidbuf[16], didbuf[16]; + const char *src_id = group_id_str(cfg, src, sidbuf, sizeof sidbuf); + const char *dst_id = group_id_str(cfg, dst, didbuf, sizeof didbuf); + + struct guardian_service svcs[16]; + size_t ns = guardian_config_expand_service(cfg, svc, svcs, 16); + for (size_t j = 0; j < ns; j++) { + const char *proto = svcs[j].proto, *port = svcs[j].port; + const char *pf = !strcmp(proto, "sctp") ? "sctp_dst" : "tp_dst"; + const char *act = is_allow(action) ? allowa : "drop"; + if (flowset_push(fs, "cookie=0x%llx,table=%d,priority=150%s,ip,%s,reg0=%s,reg1=%s,%s=%s,actions=%s", + cookie, pt, newm, proto, src_id, dst_id, pf, port, act)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=%d,priority=150%s,%s6,reg0=%s,reg1=%s,%s=%s,actions=%s", + cookie, pt, newm, proto, src_id, dst_id, pf, port, act)) return -1; + } + } + return 0; +} + +/* Emit priority-140 intra-group policy (table 2/4). */ +static int gen_intra_policy(const struct guardian_config *cfg, struct guardian_flowset *fs, + int pt, const char *newm, const char *allowa) +{ + unsigned long long cookie = (unsigned long long)GUARDIAN_COOKIE; + size_t n = guardian_config_n_intra_policy(cfg); + for (size_t i = 0; i < n; i++) { + const char *grp, *svc, *action; + guardian_config_intra_policy(cfg, i, &grp, &svc, &action); + char idbuf[16]; + const char *id = group_id_str(cfg, grp, idbuf, sizeof idbuf); + + if (is_any(svc)) { + if (is_allow(action)) { + if (pt == 4) { + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=140,ct_state=+new,ip,reg0=%s,reg1=%s,actions=%s", + cookie, id, id, allowa)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=140,ct_state=+new,ipv6,reg0=%s,reg1=%s,actions=%s", + cookie, id, id, allowa)) return -1; + } else { + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=140,reg0=%s,reg1=%s,actions=NORMAL", + cookie, id, id)) return -1; + } + } else { + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=140,reg0=%s,reg1=%s,actions=drop", + cookie, id, id)) return -1; + if (pt == 4) { + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=140,ct_state=+new,ip,reg0=%s,reg1=%s,actions=drop", + cookie, id, id)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=140,ct_state=+new,ipv6,reg0=%s,reg1=%s,actions=drop", + cookie, id, id)) return -1; + } + } + continue; + } + + struct guardian_service svcs[16]; + size_t ns = guardian_config_expand_service(cfg, svc, svcs, 16); + for (size_t j = 0; j < ns; j++) { + const char *proto = svcs[j].proto, *port = svcs[j].port; + const char *pf = !strcmp(proto, "sctp") ? "sctp_dst" : "tp_dst"; + const char *act = is_allow(action) ? allowa : "drop"; + if (flowset_push(fs, "cookie=0x%llx,table=%d,priority=140%s,ip,%s,reg0=%s,reg1=%s,%s=%s,actions=%s", + cookie, pt, newm, proto, id, id, pf, port, act)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=%d,priority=140%s,%s6,reg0=%s,reg1=%s,%s=%s,actions=%s", + cookie, pt, newm, proto, id, id, pf, port, act)) return -1; + } + } + return 0; +} + +/* Emit priority-110 ARP/ND allowance, 105 reflexive return, 100 group ANY. */ +static int gen_group_any_policy(const struct guardian_config *cfg, struct guardian_flowset *fs, + int pt) +{ + unsigned long long cookie = (unsigned long long)GUARDIAN_COOKIE; + size_t n = guardian_config_n_group_policy(cfg); + + for (size_t i = 0; i < n; i++) { + const char *src, *dst, *svc, *action; + guardian_config_group_policy(cfg, i, &src, &dst, &svc, &action); + if (!is_allow(action)) continue; + char sidbuf[16], didbuf[16]; + const char *src_id = group_id_str(cfg, src, sidbuf, sizeof sidbuf); + const char *dst_id = group_id_str(cfg, dst, didbuf, sizeof didbuf); + + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=110,arp,reg0=%s,reg1=%s,actions=NORMAL", cookie, src_id, dst_id)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=110,arp,reg0=%s,reg1=%s,actions=NORMAL", cookie, dst_id, src_id)) return -1; + for (int t = 135; t <= 136; t++) { + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=110,icmp6,icmpv6_type=%d,reg0=%s,reg1=%s,actions=NORMAL", cookie, t, src_id, dst_id)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=110,icmp6,icmpv6_type=%d,reg0=%s,reg1=%s,actions=NORMAL", cookie, t, dst_id, src_id)) return -1; + } + (void)svc; + } + + for (size_t i = 0; i < n; i++) { + const char *src, *dst, *svc, *action; + guardian_config_group_policy(cfg, i, &src, &dst, &svc, &action); + if (!is_any(svc) || !is_allow(action)) continue; + char sidbuf[16], didbuf[16]; + const char *src_id = group_id_str(cfg, src, sidbuf, sizeof sidbuf); + const char *dst_id = group_id_str(cfg, dst, didbuf, sizeof didbuf); + + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=105,icmp,reg0=%s,reg1=%s,icmp_type=0,actions=NORMAL", cookie, dst_id, src_id)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=105,icmp6,reg0=%s,reg1=%s,icmpv6_type=129,actions=NORMAL", cookie, dst_id, src_id)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=105,tcp,reg0=%s,reg1=%s,tcp_flags=+ack,actions=NORMAL", cookie, dst_id, src_id)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=105,tcp6,reg0=%s,reg1=%s,tcp_flags=+ack,actions=NORMAL", cookie, dst_id, src_id)) return -1; + } + + for (size_t i = 0; i < n; i++) { + const char *src, *dst, *svc, *action; + guardian_config_group_policy(cfg, i, &src, &dst, &svc, &action); + if (!is_any(svc)) continue; + char sidbuf[16], didbuf[16]; + const char *src_id = group_id_str(cfg, src, sidbuf, sizeof sidbuf); + const char *dst_id = group_id_str(cfg, dst, didbuf, sizeof didbuf); + + if (!is_allow(action)) { + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=100,reg0=%s,reg1=%s,actions=drop", cookie, src_id, dst_id)) return -1; + if (pt == 4) { + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=100,ct_state=+new,ip,reg0=%s,reg1=%s,actions=drop", cookie, src_id, dst_id)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=100,ct_state=+new,ipv6,reg0=%s,reg1=%s,actions=drop", cookie, src_id, dst_id)) return -1; + } + } + } + return 0; +} + +static int gen_stateful_table4(const struct guardian_config *cfg, struct guardian_flowset *fs, int pt) +{ + if (pt != 4) return 0; + unsigned long long cookie = (unsigned long long)GUARDIAN_COOKIE; + + if (guardian_config_drop_invalid(cfg)) { + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=210,ip,ct_state=+inv,actions=drop", cookie)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=210,ipv6,ct_state=+inv,actions=drop", cookie)) return -1; + } + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=200,ip,ct_state=+est,actions=NORMAL", cookie)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=200,ipv6,ct_state=+est,actions=NORMAL", cookie)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=200,ip,ct_state=+rel,actions=NORMAL", cookie)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=200,ipv6,ct_state=+rel,actions=NORMAL", cookie)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=1,ip,ct_state=+new,actions=ct(commit,zone=%d),NORMAL", cookie, GUARDIAN_CT_ZONE)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=1,ipv6,ct_state=+new,actions=ct(commit,zone=%d),NORMAL", cookie, GUARDIAN_CT_ZONE)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=4,priority=0,actions=NORMAL", cookie)) return -1; + return 0; +} + +/* Read the live FDB (port, mac) snapshot for a bridge via the same unixctl + * path ovs-appctl uses: read ovs-vswitchd's pidfile, build its .ctl socket, + * and transact "fdb/show" -- directly against libopenvswitch, no shell fork. */ +static size_t read_fdb(const char *bridge, struct fdb_entry *out, size_t max) +{ + char pidfile[256]; + snprintf(pidfile, sizeof pidfile, "%s/ovs-vswitchd.pid", ovs_rundir()); + pid_t pid = read_pidfile(pidfile); + if (pid < 0) + return 0; + + char sock[256]; + snprintf(sock, sizeof sock, "%s/ovs-vswitchd.%ld.ctl", ovs_rundir(), (long)pid); + struct jsonrpc *client = NULL; + if (unixctl_client_create(sock, &client) || !client) + return 0; + + char *result = NULL, *cmd_error = NULL; + char *argv[] = { (char *)bridge }; + int error = unixctl_client_transact(client, "fdb/show", 1, argv, + &result, &cmd_error); + jsonrpc_close(client); + if (error || cmd_error || !result) { + free(result); + free(cmd_error); + return 0; + } + + size_t n = 0; + char *save = NULL; + for (char *line = strtok_r(result, "\n", &save); line && n < max; + line = strtok_r(NULL, "\n", &save)) { + char port[16], mac[GUARDIAN_NAME_MAX]; + int vlan; long age; + if (sscanf(line, "%15s %d %63s %ld", port, &vlan, mac, &age) != 4) + continue; + (void)vlan; + (void)age; + if (!strcmp(port, "port")) continue; /* header row */ + bool numeric = true; + for (char *c = port; *c; c++) if (!isdigit((unsigned char)*c)) { numeric = false; break; } + if (!numeric) continue; + snprintf(out[n].port, sizeof out[n].port, "%s", port); + snprintf(out[n].mac, sizeof out[n].mac, "%s", mac); + n++; + } + free(result); + free(cmd_error); + return n; +} + +/* Table 3: multicast/broadcast delivered as per-recipient unicast (mc2uc). */ +static int gen_multicast_table(const struct guardian_config *cfg, struct guardian_flowset *fs, + const char *bridge) +{ + unsigned long long cookie = (unsigned long long)GUARDIAN_COOKIE; + + struct fdb_entry fdb[MAX_FDB]; + size_t n_fdb = read_fdb(bridge, fdb, MAX_FDB); + + size_t n_sg = guardian_config_n_src_groups(cfg); + for (size_t s = 0; s < n_sg; s++) { + const char *sg = guardian_config_src_group(cfg, s); + uint32_t sg_id = guardian_config_group_id(cfg, sg); + + char actions[2048]; + size_t alen = 0; + for (size_t f = 0; f < n_fdb; f++) { + const char *rgrp = guardian_config_group_of_mac(cfg, fdb[f].mac); + if (rgrp && guardian_config_policy_is_drop(cfg, sg, rgrp)) continue; + if (alen >= sizeof actions) break; + int w = snprintf(actions + alen, sizeof actions - alen, + "%smod_dl_dst:%s,output:%s", + alen ? "," : "", fdb[f].mac, fdb[f].port); + if (w < 0 || (size_t)w >= sizeof actions - alen) { alen = sizeof actions; break; } + alen += (size_t)w; + } + if (flowset_push(fs, "cookie=0x%llx,table=3,priority=100,reg0=%u,actions=%s", + cookie, sg_id, alen ? actions : "drop")) + return -1; + + /* Service-scoped multicast exceptions (re-include recipients allowed + * for a specific service even if the coarse group policy is DROP). */ + size_t n_gp = guardian_config_n_group_policy(cfg); + for (size_t i = 0; i < n_gp; i++) { + const char *src, *dst, *svc, *action; + guardian_config_group_policy(cfg, i, &src, &dst, &svc, &action); + if (strcasecmp(src, sg) || !is_allow(action) || is_any(svc)) continue; + + struct guardian_service svcs[16]; + size_t ns = guardian_config_expand_service(cfg, svc, svcs, 16); + for (size_t j = 0; j < ns; j++) { + const char *proto = svcs[j].proto, *port = svcs[j].port; + const char *pf = !strcmp(proto, "sctp") ? "sctp_dst" : "tp_dst"; + + char pacts[2048]; + size_t plen = 0; + for (size_t f = 0; f < n_fdb; f++) { + const char *rgrp = guardian_config_group_of_mac(cfg, fdb[f].mac); + if (rgrp && guardian_config_policy_is_drop(cfg, sg, rgrp) && + !guardian_config_service_allows_port(cfg, sg, rgrp, proto, port)) + continue; + if (plen >= sizeof pacts) break; + int w = snprintf(pacts + plen, sizeof pacts - plen, + "%smod_dl_dst:%s,output:%s", + plen ? "," : "", fdb[f].mac, fdb[f].port); + if (w < 0 || (size_t)w >= sizeof pacts - plen) { plen = sizeof pacts; break; } + plen += (size_t)w; + } + const char *acts = plen ? pacts : "drop"; + if (flowset_push(fs, "cookie=0x%llx,table=3,priority=110,reg0=%u,%s,%s=%s,actions=%s", + cookie, sg_id, proto, pf, port, acts)) return -1; + if (flowset_push(fs, "cookie=0x%llx,table=3,priority=110,reg0=%u,%s6,%s=%s,actions=%s", + cookie, sg_id, proto, pf, port, acts)) return -1; + } + } + } + if (flowset_push(fs, "cookie=0x%llx,table=3,priority=0,actions=NORMAL", cookie)) + return -1; + return 0; +} + struct guardian_flowset *guardian_flows_generate(const struct guardian_config *cfg) { - (void)cfg; struct guardian_flowset *fs = calloc(1, sizeof *fs); if (!fs) return NULL; - char buf[512]; + bool stateful = guardian_config_stateful(cfg); + int pt = stateful ? 4 : 2; + const char *newm = stateful ? ",ct_state=+new" : ""; + char allowa[64]; + if (stateful) + snprintf(allowa, sizeof allowa, "ct(commit,zone=%d),NORMAL", GUARDIAN_CT_ZONE); + else + snprintf(allowa, sizeof allowa, "NORMAL"); + + if (gen_device_tables(cfg, fs)) goto fail; + + if (pt == 4) { + unsigned long long cookie = (unsigned long long)GUARDIAN_COOKIE; + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=200,ip,ct_state=-trk,actions=ct(table=4,zone=%d)", cookie, GUARDIAN_CT_ZONE)) goto fail; + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=200,ipv6,ct_state=-trk,actions=ct(table=4,zone=%d)", cookie, GUARDIAN_CT_ZONE)) goto fail; + } + + if (gen_device_policy(cfg, fs, pt, newm, allowa)) goto fail; + if (gen_group_service_policy(cfg, fs, pt, newm, allowa)) goto fail; + if (gen_intra_policy(cfg, fs, pt, newm, allowa)) goto fail; + if (gen_group_any_policy(cfg, fs, pt)) goto fail; - /* Unmanaged sources still traverse the pipeline for policy evaluation. */ - snprintf(buf, sizeof buf, - "cookie=0x%llx,table=0,priority=1,actions=resubmit(,1)", - (unsigned long long)GUARDIAN_COOKIE); - if (flowset_push(fs, buf)) goto fail; + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=0,actions=NORMAL", (unsigned long long)GUARDIAN_COOKIE)) goto fail; - /* TODO: per-device table-0 (dl_src->reg0) and table-1 (dl_dst->reg1) flows, - * then tables 2/3/4 policy hierarchy. Drives off cfg's group/device tables. */ + if (gen_stateful_table4(cfg, fs, pt)) goto fail; + if (gen_multicast_table(cfg, fs, GUARDIAN_BRIDGE)) goto fail; return fs;