diff --git a/configure.ac b/configure.ac index 67fc3ca..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 @@ -85,6 +86,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..7dd36ee --- /dev/null +++ b/source/guardian/Makefile.am @@ -0,0 +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 $(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 new file mode 100755 index 0000000..f76e2fb --- /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..91d8169 --- /dev/null +++ b/source/guardian/guardian_config.c @@ -0,0 +1,355 @@ +/* + * Guardian config parsing. + * + * 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 + */ +#include "guardian_config.h" + +#include +#include +#include +#include +#include + +#define MAX_GROUPS 64 +#define MAX_DEVICES 512 +#define MAX_SERVICES 128 +#define MAX_MACROS 64 +#define MAX_POLICY 256 + +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; + 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 + * 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; +} + +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"); + 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[GUARDIAN_NAME_MAX] = ""; + + 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[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[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[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"); + } + } + } + + 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; +} + +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 new file mode 100755 index 0000000..14c0e47 --- /dev/null +++ b/source/guardian/guardian_config.h @@ -0,0 +1,82 @@ +/* + * Guardian config parsing + accessors. + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef GUARDIAN_CONFIG_H +#define GUARDIAN_CONFIG_H + +#include +#include +#include + +#define GUARDIAN_NAME_MAX 64 + +/* 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); + +/* [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 new file mode 100755 index 0000000..65a0aac --- /dev/null +++ b/source/guardian/guardian_flows.c @@ -0,0 +1,499 @@ +/* + * Guardian policy -> OpenFlow flow translation (port of gen_flows). + * + * 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 + */ +#include "guardian_flows.h" +#include "guardian.h" +#include "guardian_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 *); + +#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(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) +{ + struct guardian_flowset *fs = calloc(1, sizeof *fs); + if (!fs) return NULL; + + 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; + + if (flowset_push(fs, "cookie=0x%llx,table=2,priority=0,actions=NORMAL", (unsigned long long)GUARDIAN_COOKIE)) goto fail; + + if (gen_stateful_table4(cfg, fs, pt)) goto fail; + if (gen_multicast_table(cfg, fs, GUARDIAN_BRIDGE)) goto fail; + + 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..ef043b2 --- /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..acddf20 --- /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..a2754a4 --- /dev/null +++ b/source/guardian/guardian_ovs.c @@ -0,0 +1,184 @@ +/* + * 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, strerror(error)); + return -1; + } + error = vconn_connect_block(*vconnp, -1); + if (error) { + fprintf(stderr, "guardian: connect to %s failed: %s\n", + name, 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", 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", 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 new file mode 100755 index 0000000..deceaf4 --- /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 */