/*===================================================================== * hellgate_page.c v2 - hellmon page 4: HELLGATE (talks to hellgated) * * #include'd from hellmon.c * NOT a standalone TU: relies on hellmon's sbuf_t/sb_addf, the ctr * registry, color globals, g_interval, g_wan/g_lan, read_file_str(). * * v2: hellmon and hellgate now TALK. Division of labor: * * data plane (every tick, hot): pinned BPF maps, read directly * via raw bpf(2) syscalls - stats sum + rule enumeration. * No IPC, no forks, no libbpf. * * control plane (rare, cold): /run/hellgate.sock line protocol * to the hellgated daemon - status, attach/detach/move/seed, * block/unblock, drop-port/undrop-port. The daemon is the * single enforcement point for guard rails (dport 22, WG * listen-ports), so every client gets the same "no". * * v1 forked `ip -d link` 1-2x per tick plus hellgate.sh/system() per * action. v2 forks NOTHING - at -i 2 that's ~43k fork/execs per day * gone, replaced by one persistent unix socket and a few syscalls. * * Keys (page 4, normal mode): * tab switch pane (prefixes <-> ports) * j/k move selection (arrows too) * a add prefix (typed CIDR) f add port (dropdown) * x delete selected entry * i NIC menu (move gate / repoint hellmon WAN/LAN) * A attach D detach R re-seed bogons * Modal: j/k + enter, esc cancels. *=====================================================================*/ #include #include #include #include #include #include #define HG_PIN "/sys/fs/bpf/hellgate" #define HG_SOCK "/run/hellgate.sock" #define HG_MAX_PFX 256 #define HG_MAX_PORTS 64 #define HG_MAX_NICS 16 #define HG_MAX_GATES 8 #define HG_IN_MAX 48 #define HG_MSG_MAX 160 #define HG_RESP_MAX 4096 #define HG_TIMEOUT_MS 700 /* must mirror hellgate.bpf.c */ enum { HG_ST_PASS = 0, HG_ST_DROP_SRC, HG_ST_DROP_DPORT, HG_ST_PASS_NONIP, HG_ST_DROP_MALFORMED, HG_ST_MAX }; struct hg_lpm_key { __u32 prefixlen; __u32 addr; }; /* addr = BE */ /*----- raw bpf(2): the per-tick fast path ---------------------------*/ static int hg_bpf(int cmd, union bpf_attr *a, unsigned sz){ return (int)syscall(__NR_bpf, cmd, a, sz); } static int hg_obj_get(const char *path){ union bpf_attr a; memset(&a, 0, sizeof a); a.pathname = (__u64)(unsigned long)path; return hg_bpf(BPF_OBJ_GET, &a, sizeof a); } static int hg_map_lookup(int fd, const void *k, void *v){ union bpf_attr a; memset(&a, 0, sizeof a); a.map_fd = (__u32)fd; a.key = (__u64)(unsigned long)k; a.value = (__u64)(unsigned long)v; return hg_bpf(BPF_MAP_LOOKUP_ELEM, &a, sizeof a); } static int hg_map_next(int fd, const void *k, void *next){ union bpf_attr a; memset(&a, 0, sizeof a); a.map_fd = (__u32)fd; a.key = (__u64)(unsigned long)k; /* NULL -> first key */ a.next_key = (__u64)(unsigned long)next; return hg_bpf(BPF_MAP_GET_NEXT_KEY, &a, sizeof a); } /*----- state --------------------------------------------------------*/ static int g_hg_fd_drop = -1, g_hg_fd_ports = -1, g_hg_fd_stats = -1; static int g_hg_ncpu = 0; static int g_hgc = -1; /* socket to hellgated */ enum { HGM_NORMAL = 0, HGM_IN_CIDR, HGM_IN_PORT, HGM_MENU_PORT, HGM_MENU_NIC, HGM_MENU_NICACT }; static int g_hg_mode = HGM_NORMAL; static int g_hg_pane = 0; /* 0 = prefixes, 1 = ports */ static int g_hg_sel = 0; static int g_hg_msel = 0; static char g_hg_in[HG_IN_MAX]; static int g_hg_inlen = 0; static char g_hg_msg[HG_MSG_MAX]; static int g_hg_force_redisc = 0; /* main loop checks this */ static struct hg_lpm_key g_hg_pfx[HG_MAX_PFX]; static int g_hg_npfx = 0; static __u16 g_hg_port[HG_MAX_PORTS]; static int g_hg_nport = 0; /* daemon-reported status, refreshed each tick */ static char g_hg_gif[IFNAME_LEN] = "?"; static int g_hg_att = 0; static char g_hg_attmode[16] = "-"; static int g_hg_daemon_ok = 0; typedef struct { char ifname[IFNAME_LEN]; char mode[16]; } hg_gate_t; static hg_gate_t g_hg_gate[HG_MAX_GATES]; static int g_hg_ngate = 0; typedef struct { char ifname[IFNAME_LEN]; char driver[32]; char state[16]; long speed; int has_gate; } hg_nic_t; static hg_nic_t g_hg_nic[HG_MAX_NICS]; static int g_hg_nnic = 0; static int g_hg_nic_pick = 0; static const struct { unsigned short port; const char *label; } HG_PORT_PRESETS[] = { { 23, "telnet scan" }, { 445, "smb" }, { 3389, "rdp" }, { 1433, "mssql" }, { 161, "snmp scan" }, { 8291, "winbox scan" }, { 5060, "sip scan (safe: Telnyx trunk is registering)" }, { 0, "custom port..." }, }; #define HG_NPRESET ((int)(sizeof HG_PORT_PRESETS / sizeof HG_PORT_PRESETS[0])) static const char *HG_NIC_ACTIONS[] = { "move hellgate to this NIC", "set as hellmon WAN", "set as hellmon LAN", "cancel", }; #define HG_NACT 4 static void hg_msgf(const char *fmt, ...){ va_list ap; va_start(ap, fmt); vsnprintf(g_hg_msg, sizeof g_hg_msg, fmt, ap); va_end(ap); } /*----- socket client ------------------------------------------------*/ static void hg_disconnect(void){ if (g_hgc >= 0) close(g_hgc); g_hgc = -1; g_hg_daemon_ok = 0; } static int hg_connect(void){ if (g_hgc >= 0) return 0; int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) return -1; struct sockaddr_un sa; memset(&sa, 0, sizeof sa); sa.sun_family = AF_UNIX; snprintf(sa.sun_path, sizeof sa.sun_path, "%s", HG_SOCK); if (connect(fd, (struct sockaddr *)&sa, sizeof sa) < 0) { close(fd); return -1; } fcntl(fd, F_SETFL, O_NONBLOCK); g_hgc = fd; return 0; } /* send one command, collect data lines into out (may be NULL), * return 0 on OK, -1 on ERR (first ERR text -> g_hg_msg), * -2 on daemon unreachable / timeout */ static int hg_req(const char *cmd, char *out, size_t outsz){ if (hg_connect()) return -2; char line[HG_IN_MAX + 16]; int n = snprintf(line, sizeof line, "%s\n", cmd); if (write(g_hgc, line, (size_t)n) != n) { hg_disconnect(); if (hg_connect() || write(g_hgc, line, (size_t)n) != n) { hg_disconnect(); return -2; } } static char rb[HG_RESP_MAX]; int rl = 0; size_t outlen = 0; if (out && outsz) out[0] = 0; for (;;) { /* one full line available? */ char *nl = memchr(rb, '\n', (size_t)rl); if (nl) { *nl = 0; int consumed = (int)(nl + 1 - rb); if (!strncmp(rb, "OK", 2)) { memmove(rb, nl + 1, (size_t)(rl - consumed)); return 0; } if (!strncmp(rb, "ERR", 3)) { hg_msgf("%s", rb[3] == ' ' ? rb + 4 : rb); memmove(rb, nl + 1, (size_t)(rl - consumed)); return -1; } if (out) { size_t l = strlen(rb); if (outlen + l + 2 < outsz) { memcpy(out + outlen, rb, l); outlen += l; out[outlen++] = '\n'; out[outlen] = 0; } } memmove(rb, nl + 1, (size_t)(rl - consumed)); rl -= consumed; continue; } struct pollfd pf = { .fd = g_hgc, .events = POLLIN }; int pr = poll(&pf, 1, HG_TIMEOUT_MS); if (pr <= 0) { hg_disconnect(); return -2; } ssize_t r = read(g_hgc, rb + rl, sizeof rb - 1 - (size_t)rl); if (r <= 0) { hg_disconnect(); return -2; } rl += (int)r; rb[rl] = 0; if (rl >= (int)sizeof rb - 1) { hg_disconnect(); return -2; } } } /* per-tick: refresh gate status from the daemon */ static void hg_refresh_status(void){ char out[1024]; g_hg_ngate = 0; int rc = hg_req("status", out, sizeof out); g_hg_daemon_ok = (rc != -2); if (rc != 0) return; char *save = NULL; for (char *ln = strtok_r(out, "\n", &save); ln; ln = strtok_r(NULL, "\n", &save)) { char a[64], b[32]; int flag; if (sscanf(ln, "iface %63s", a) == 1) snprintf(g_hg_gif, sizeof g_hg_gif, "%.15s", a); else if (sscanf(ln, "attached %d %31s", &flag, b) == 2) { g_hg_att = flag; snprintf(g_hg_attmode, sizeof g_hg_attmode, "%.15s", b); } else if (sscanf(ln, "gate %63s %31s", a, b) == 2 && g_hg_ngate < HG_MAX_GATES) { snprintf(g_hg_gate[g_hg_ngate].ifname, sizeof g_hg_gate[0].ifname, "%.15s", a); snprintf(g_hg_gate[g_hg_ngate].mode, sizeof g_hg_gate[0].mode, "%.15s", b); g_hg_ngate++; } } } static int hg_if_has_gate(const char *ifname){ for (int i = 0; i < g_hg_ngate; i++) if (!strcmp(g_hg_gate[i].ifname, ifname)) return 1; return 0; } /*----- map fast path ------------------------------------------------*/ static int hg_possible_cpus(void){ char buf[64]; if (read_file_str("/sys/devices/system/cpu/possible", buf, sizeof buf) < 0) return (int)sysconf(_SC_NPROCESSORS_CONF); int lo = 0, hi = 0; if (sscanf(buf, "%d-%d", &lo, &hi) == 2) return hi - lo + 1; if (sscanf(buf, "%d", &lo) == 1) return 1; return (int)sysconf(_SC_NPROCESSORS_CONF); } static void hg_close_maps(void){ if (g_hg_fd_drop >= 0) close(g_hg_fd_drop); if (g_hg_fd_ports >= 0) close(g_hg_fd_ports); if (g_hg_fd_stats >= 0) close(g_hg_fd_stats); g_hg_fd_drop = g_hg_fd_ports = g_hg_fd_stats = -1; } static void hg_open_maps(void){ if (!g_hg_ncpu) g_hg_ncpu = hg_possible_cpus(); if (g_hg_fd_drop < 0) g_hg_fd_drop = hg_obj_get(HG_PIN "/hellgate_drop4"); if (g_hg_fd_ports < 0) g_hg_fd_ports = hg_obj_get(HG_PIN "/hellgate_dports"); if (g_hg_fd_stats < 0) g_hg_fd_stats = hg_obj_get(HG_PIN "/hellgate_stats"); } static void hg_load_prefixes(void){ g_hg_npfx = 0; if (g_hg_fd_drop < 0) return; struct hg_lpm_key k, next; void *cur = NULL; while (g_hg_npfx < HG_MAX_PFX && hg_map_next(g_hg_fd_drop, cur, &next) == 0) { k = next; g_hg_pfx[g_hg_npfx++] = k; cur = &k; } } static void hg_load_ports(void){ g_hg_nport = 0; if (g_hg_fd_ports < 0) return; __u16 k, next; void *cur = NULL; while (g_hg_nport < HG_MAX_PORTS && hg_map_next(g_hg_fd_ports, cur, &next) == 0) { k = next; g_hg_port[g_hg_nport++] = k; cur = &k; } } static int hg_read_stats(unsigned long long out[HG_ST_MAX]){ if (g_hg_fd_stats < 0) return -1; static __u64 *buf = NULL; if (!buf) buf = calloc((size_t)g_hg_ncpu, sizeof *buf); /* once */ if (!buf) return -1; for (__u32 i = 0; i < HG_ST_MAX; i++) { out[i] = 0; if (hg_map_lookup(g_hg_fd_stats, &i, buf) != 0) { hg_close_maps(); /* stale fds after daemon re-pin */ return -1; } for (int c = 0; c < g_hg_ncpu; c++) out[i] += buf[c]; } return 0; } /*----- NIC scan (sysfs only, fork-free) -----------------------------*/ static int hg_ifname_ok(const char *s){ if (!*s || strlen(s) >= IFNAME_LEN) return 0; for (; *s; s++) if (!(isalnum((unsigned char)*s) || *s=='_' || *s=='-' || *s=='.')) return 0; return 1; } static void hg_scan_nics(void){ g_hg_nnic = 0; DIR *d = opendir("/sys/class/net"); if (!d) return; struct dirent *de; while ((de = readdir(d)) && g_hg_nnic < HG_MAX_NICS) { if (de->d_name[0] == '.') continue; if (!hg_ifname_ok(de->d_name)) continue; char p[300], lnk[256]; snprintf(p, sizeof p, "/sys/class/net/%s/device", de->d_name); if (access(p, F_OK) != 0) continue; /* virtual: wg, vlan.. */ hg_nic_t *nc = &g_hg_nic[g_hg_nnic]; memset(nc, 0, sizeof *nc); snprintf(nc->ifname, sizeof nc->ifname, "%s", de->d_name); snprintf(p, sizeof p, "/sys/class/net/%s/device/driver", de->d_name); ssize_t l = readlink(p, lnk, sizeof lnk - 1); if (l > 0) { lnk[l] = 0; char *b = strrchr(lnk, '/'); snprintf(nc->driver, sizeof nc->driver, "%.31s", b ? b + 1 : lnk); } else snprintf(nc->driver, sizeof nc->driver, "?"); snprintf(p, sizeof p, "/sys/class/net/%s/operstate", de->d_name); if (read_file_str(p, nc->state, sizeof nc->state) < 0) snprintf(nc->state, sizeof nc->state, "?"); snprintf(p, sizeof p, "/sys/class/net/%s/speed", de->d_name); char sb[32]; nc->speed = read_file_str(p, sb, sizeof sb) == 0 ? atol(sb) : -1; nc->has_gate = hg_if_has_gate(nc->ifname); g_hg_nnic++; } closedir(d); } /*----- actions via the daemon ---------------------------------------*/ static void hg_action(const char *cmd, const char *okmsg){ char out[512]; int rc = hg_req(cmd, out, sizeof out); if (rc == 0) hg_msgf("%s", okmsg); else if (rc == -2) hg_msgf("hellgated unreachable - service hellgate start"); /* rc == -1: daemon's ERR text already in g_hg_msg */ hg_refresh_status(); hg_close_maps(); /* attach/move can re-pin: reopen */ } static void hg_add_port(unsigned p){ char cmd[48], ok[64]; snprintf(cmd, sizeof cmd, "drop-port %u", p); snprintf(ok, sizeof ok, "dropping WAN dport %u at XDP", p); hg_action(cmd, ok); } /*----- render -------------------------------------------------------*/ static void hg_stat_line(sbuf_t *b, const char *lbl, const char *key, unsigned long long v, int hot){ ctr_t *c = ctr_get(key); if (!c->init) { c->base = c->prev = v; c->init = 1; } long long d = (long long)v - (long long)c->prev; c->cur = v; const char *col = d > 0 ? (hot ? RED : YEL) : DIM; sb_addf(b, " %-14s %s%llu", lbl, col, v); if (d > 0) sb_addf(b, " (+%lld/%ds)", d, g_interval); sb_addf(b, "%s\n", RST); if (hot && d > 0) alert("hellgate %s +%lld", lbl, d); } static void hg_render(sbuf_t *b, double dt){ (void)dt; hg_open_maps(); hg_refresh_status(); sb_addf(b, "%s=== HELLGATE: XDP early-drop gate ===%s", BOLD, RST); sb_addf(b, " daemon: %s%s%s\n", g_hg_daemon_ok ? GRN : RED, g_hg_daemon_ok ? "up" : "DOWN", RST); if (!g_hg_daemon_ok) sb_addf(b, "%s(control offline - service hellgate start; pinned maps " "still readable below)%s\n", YEL, RST); else if (g_hg_att) sb_addf(b, "gate: %sATTACHED%s (%s) on %s\n", GRN, RST, g_hg_attmode, g_hg_gif); else sb_addf(b, "gate: %sNOT ATTACHED%s on %s (A = attach)\n", RED, RST, g_hg_gif); if (g_hg_att && !strcmp(g_hg_attmode, "xdpgeneric")) sb_addf(b, "%s!! generic mode: pre-skb benefit lost - check i40e/" "queue setup%s\n", YEL, RST); unsigned long long st[HG_ST_MAX]; if (hg_read_stats(st) == 0) { hg_stat_line(b, "passed", "hg.pass", st[HG_ST_PASS], 0); hg_stat_line(b, "passed non-ip", "hg.nonip", st[HG_ST_PASS_NONIP], 0); hg_stat_line(b, "dropped src", "hg.dropsrc", st[HG_ST_DROP_SRC], 0); hg_stat_line(b, "dropped dport", "hg.dropport", st[HG_ST_DROP_DPORT], 0); hg_stat_line(b, "malformed", "hg.malformed",st[HG_ST_DROP_MALFORMED], 1); } else { sb_addf(b, "%spinned maps not readable under %s%s\n", YEL, HG_PIN, RST); } hg_load_prefixes(); hg_load_ports(); if (g_hg_pane == 0 && g_hg_sel >= g_hg_npfx) g_hg_sel = g_hg_npfx ? g_hg_npfx - 1 : 0; if (g_hg_pane == 1 && g_hg_sel >= g_hg_nport) g_hg_sel = g_hg_nport ? g_hg_nport - 1 : 0; sb_addf(b, "\n%s--- drop prefixes (%d)%s ---%s\n", BOLD, g_hg_npfx, g_hg_pane == 0 ? " [active pane]" : "", RST); for (int i = 0; i < g_hg_npfx; i++) { char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &g_hg_pfx[i].addr, ip, sizeof ip); int cur = (g_hg_pane == 0 && i == g_hg_sel); sb_addf(b, " %s%s %s/%u%s\n", cur ? BOLD : DIM, cur ? ">" : " ", ip, g_hg_pfx[i].prefixlen, RST); } if (!g_hg_npfx) sb_addf(b, " %s(empty - R to seed bogons)%s\n", DIM, RST); sb_addf(b, "%s--- drop dports (%d)%s ---%s\n", BOLD, g_hg_nport, g_hg_pane == 1 ? " [active pane]" : "", RST); for (int i = 0; i < g_hg_nport; i++) { const char *lbl = ""; for (int j = 0; j < HG_NPRESET; j++) if (HG_PORT_PRESETS[j].port == g_hg_port[i]) lbl = HG_PORT_PRESETS[j].label; int cur = (g_hg_pane == 1 && i == g_hg_sel); sb_addf(b, " %s%s %u %s%s\n", cur ? BOLD : DIM, cur ? ">" : " ", g_hg_port[i], lbl, RST); } if (!g_hg_nport) sb_addf(b, " %s(empty = dport gate off)%s\n", DIM, RST); sb_addf(b, "\n%stab=pane j/k=sel a=add-prefix f=add-port x=del " "i=NICs A=attach D=detach R=seed%s\n", DIM, RST); if (g_hg_msg[0]) sb_addf(b, "%s>> %s%s\n", YEL, g_hg_msg, RST); /* modal overlays */ if (g_hg_mode == HGM_IN_CIDR || g_hg_mode == HGM_IN_PORT) { sb_addf(b, "\n%s%s: %s_%s (enter=ok esc=cancel)\n", BOLD, g_hg_mode == HGM_IN_CIDR ? "block prefix (a.b.c.d/len)" : "custom dport", g_hg_in, RST); } else if (g_hg_mode == HGM_MENU_PORT) { sb_addf(b, "\n%sdrop which dport? (j/k, enter, esc)%s\n", BOLD, RST); for (int i = 0; i < HG_NPRESET; i++) { int cur = (i == g_hg_msel); if (HG_PORT_PRESETS[i].port) sb_addf(b, " %s%s %5u %s%s\n", cur ? BOLD : DIM, cur ? ">" : " ", HG_PORT_PRESETS[i].port, HG_PORT_PRESETS[i].label, RST); else sb_addf(b, " %s%s %s%s\n", cur ? BOLD : DIM, cur ? ">" : " ", HG_PORT_PRESETS[i].label, RST); } } else if (g_hg_mode == HGM_MENU_NIC) { sb_addf(b, "\n%sNICs in this system (j/k, enter, esc)%s\n", BOLD, RST); for (int i = 0; i < g_hg_nnic; i++) { hg_nic_t *nc = &g_hg_nic[i]; int cur = (i == g_hg_msel); sb_addf(b, " %s%s %-14s %-10s %-6s ", cur ? BOLD : DIM, cur ? ">" : " ", nc->ifname, nc->driver, nc->state); if (nc->speed > 0) sb_addf(b, "%ldMb ", nc->speed); if (nc->has_gate) sb_addf(b, "%s[gate]%s", GRN, RST); if (!strcmp(nc->ifname, g_wan)) sb_addf(b, "%s[hellmon:WAN]%s", YEL, RST); if (!strcmp(nc->ifname, g_lan)) sb_addf(b, "%s[hellmon:LAN]%s", YEL, RST); sb_addf(b, "%s\n", RST); } if (!g_hg_nnic) sb_addf(b, " %s(no physical NICs found?)%s\n", DIM, RST); } else if (g_hg_mode == HGM_MENU_NICACT) { sb_addf(b, "\n%s%s: (j/k, enter, esc)%s\n", BOLD, g_hg_nic[g_hg_nic_pick].ifname, RST); for (int i = 0; i < HG_NACT; i++) sb_addf(b, " %s%s %s%s\n", i == g_hg_msel ? BOLD : DIM, i == g_hg_msel ? ">" : " ", HG_NIC_ACTIONS[i], RST); } } /*----- input --------------------------------------------------------*/ static void hg_commit_input(void){ g_hg_in[g_hg_inlen] = 0; if (g_hg_mode == HGM_IN_CIDR) { char cmd[HG_IN_MAX + 8], ok[HG_IN_MAX + 32]; snprintf(cmd, sizeof cmd, "block %s", g_hg_in); snprintf(ok, sizeof ok, "dropping src %s at XDP", g_hg_in); hg_action(cmd, ok); } else if (g_hg_mode == HGM_IN_PORT) { hg_add_port((unsigned)atoi(g_hg_in)); } g_hg_mode = HGM_NORMAL; g_hg_inlen = 0; g_hg_in[0] = 0; } static void hg_delete_selected(void){ char cmd[64], ok[80]; if (g_hg_pane == 0) { if (g_hg_sel >= g_hg_npfx) { hg_msgf("nothing selected"); return; } struct hg_lpm_key k = g_hg_pfx[g_hg_sel]; char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &k.addr, ip, sizeof ip); snprintf(cmd, sizeof cmd, "unblock %s/%u", ip, k.prefixlen); snprintf(ok, sizeof ok, "unblocked %s/%u", ip, k.prefixlen); } else { if (g_hg_sel >= g_hg_nport) { hg_msgf("nothing selected"); return; } snprintf(cmd, sizeof cmd, "undrop-port %u", g_hg_port[g_hg_sel]); snprintf(ok, sizeof ok, "no longer dropping dport %u", g_hg_port[g_hg_sel]); } hg_action(cmd, ok); } /* returns 1 if consumed (caller re-renders + repaints) */ static int hg_handle_key(unsigned char c){ if (g_hg_mode == HGM_IN_CIDR || g_hg_mode == HGM_IN_PORT) { if (c == 27) { g_hg_mode = HGM_NORMAL; g_hg_inlen = 0; g_hg_in[0] = 0; } else if (c == '\r' || c == '\n') hg_commit_input(); else if (c == 127 || c == 8) { if (g_hg_inlen) g_hg_in[--g_hg_inlen] = 0; } else if ((isdigit(c) || c == '.' || c == '/') && g_hg_inlen + 1 < HG_IN_MAX) { g_hg_in[g_hg_inlen++] = (char)c; g_hg_in[g_hg_inlen] = 0; } return 1; } if (g_hg_mode == HGM_MENU_PORT || g_hg_mode == HGM_MENU_NIC || g_hg_mode == HGM_MENU_NICACT) { int nitems = g_hg_mode == HGM_MENU_PORT ? HG_NPRESET : g_hg_mode == HGM_MENU_NIC ? g_hg_nnic : HG_NACT; if (c == 27 || c == 'q') { g_hg_mode = HGM_NORMAL; return 1; } if (c == 'j' && g_hg_msel + 1 < nitems) { g_hg_msel++; return 1; } if (c == 'k' && g_hg_msel > 0) { g_hg_msel--; return 1; } if (c == '\r' || c == '\n') { if (g_hg_mode == HGM_MENU_PORT) { if (HG_PORT_PRESETS[g_hg_msel].port == 0) { g_hg_mode = HGM_IN_PORT; g_hg_inlen = 0; g_hg_in[0] = 0; } else { hg_add_port(HG_PORT_PRESETS[g_hg_msel].port); g_hg_mode = HGM_NORMAL; } } else if (g_hg_mode == HGM_MENU_NIC) { if (!g_hg_nnic) { g_hg_mode = HGM_NORMAL; return 1; } g_hg_nic_pick = g_hg_msel; g_hg_msel = 0; g_hg_mode = HGM_MENU_NICACT; } else { hg_nic_t *nc = &g_hg_nic[g_hg_nic_pick]; if (g_hg_msel == 0) { char cmd[IFNAME_LEN + 8], ok[IFNAME_LEN + 24]; snprintf(cmd, sizeof cmd, "move %s", nc->ifname); snprintf(ok, sizeof ok, "gate moved to %s", nc->ifname); hg_action(cmd, ok); } else if (g_hg_msel == 1) { g_wan = strdup(nc->ifname); g_hg_force_redisc = 1; hg_msgf("hellmon WAN -> %s (rediscovering)", nc->ifname); } else if (g_hg_msel == 2) { g_lan = strdup(nc->ifname); g_hg_force_redisc = 1; hg_msgf("hellmon LAN -> %s (rediscovering)", nc->ifname); } g_hg_mode = HGM_NORMAL; } return 1; } return 1; /* eat strays: menus feel modal */ } switch (c) { case '\t': g_hg_pane = !g_hg_pane; g_hg_sel = 0; return 1; case 'j': g_hg_sel++; return 1; case 'k': if (g_hg_sel > 0) g_hg_sel--; return 1; case 'a': g_hg_mode = HGM_IN_CIDR; g_hg_inlen = 0; g_hg_in[0] = 0; return 1; case 'f': g_hg_mode = HGM_MENU_PORT; g_hg_msel = 0; return 1; case 'x': hg_delete_selected(); return 1; case 'i': hg_refresh_status(); hg_scan_nics(); g_hg_mode = HGM_MENU_NIC; g_hg_msel = 0; return 1; case 'A': hg_action("attach", "attached"); return 1; case 'D': hg_action("detach", "detached"); return 1; case 'R': hg_action("seed", "bogons seeded"); return 1; default: return 0; } }