Import nftables maps: one port forward or rule per element
A DNAT map (named or anonymous) becomes one port forward per element, named after the map; a verdict map becomes one rule per element, in place, with jumps inlined. A lookup matches one element at most, so the expanded rules do the same. A named map that can't be expanded (keyed by several fields) is left out with a note instead of a raw rule naming it, which nftables would refuse. Test fixture made with nft 1.1.3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eaadbe6a3d
commit
ebec2cfcca
7 files changed
+340
-4
No files matched your search
@@ -81,6 +81,16 @@ type nftSet struct {
|
||||
Elem []json.RawMessage `json:"elem"`
|
||||
}
|
||||
|
||||
// nftMap is a named map: its elements are [key, value] pairs.
|
||||
type nftMap struct {
|
||||
Family string `json:"family"`
|
||||
Table string `json:"table"`
|
||||
Name string `json:"name"`
|
||||
Type json.RawMessage `json:"type"`
|
||||
Map json.RawMessage `json:"map"`
|
||||
Elem []json.RawMessage `json:"elem"`
|
||||
}
|
||||
|
||||
type nftFlowtable struct {
|
||||
Table string `json:"table"`
|
||||
Name string `json:"name"`
|
||||
@@ -93,6 +103,7 @@ type importer struct {
|
||||
chains map[string]nftChain
|
||||
rules map[string][]nftRule // by chain, in order
|
||||
sets map[string]nftSet
|
||||
maps map[string]nftMap
|
||||
flow []nftFlowtable
|
||||
text map[int]string // rule text by handle
|
||||
st *model.State
|
||||
@@ -120,7 +131,7 @@ func Import(in Input) (*Result, error) {
|
||||
if err := json.Unmarshal(in.JSON, &doc); err != nil {
|
||||
return nil, fmt.Errorf("the input isn't nft's JSON (nft -j list ruleset): %v", err)
|
||||
}
|
||||
im := &importer{in: in, chains: map[string]nftChain{}, rules: map[string][]nftRule{}, sets: map[string]nftSet{},
|
||||
im := &importer{in: in, chains: map[string]nftChain{}, rules: map[string][]nftRule{}, sets: map[string]nftSet{}, maps: map[string]nftMap{},
|
||||
text: map[int]string{}, st: model.NewState(), used: map[string]bool{}, noted: map[string]bool{}, positions: map[string]float64{}}
|
||||
var tables []nftTable
|
||||
for _, item := range doc.Nftables {
|
||||
@@ -172,6 +183,12 @@ func Import(in Input) (*Result, error) {
|
||||
if ours(s.Family, s.Table) {
|
||||
im.sets[s.Name] = s
|
||||
}
|
||||
case item["map"] != nil:
|
||||
var m nftMap
|
||||
json.Unmarshal(item["map"], &m)
|
||||
if ours(m.Family, m.Table) {
|
||||
im.maps[m.Name] = m
|
||||
}
|
||||
case item["flowtable"] != nil:
|
||||
var f nftFlowtable
|
||||
json.Unmarshal(item["flowtable"], &f)
|
||||
@@ -465,6 +482,20 @@ type parsed struct {
|
||||
unsupported []string
|
||||
comment string
|
||||
handle int
|
||||
// mapUse: a dnat to a map, or a verdict map, still to be expanded
|
||||
// into one rule per element (expandMaps).
|
||||
mapUse *mapUse
|
||||
// name: a port forward's name, when the rule's comment won't do
|
||||
// (one of a map's elements).
|
||||
name string
|
||||
}
|
||||
|
||||
// mapUse is a lookup in a map: "dnat ip to tcp dport map @forwards" or
|
||||
// "tcp dport vmap { 22 : accept }".
|
||||
type mapUse struct {
|
||||
kind string // dnat or vmap
|
||||
key json.RawMessage // what is looked up: {"payload": ...}, {"meta": ...}
|
||||
data json.RawMessage // "@name", or {"set": [[key, value], ...]}
|
||||
}
|
||||
|
||||
type match struct {
|
||||
@@ -536,6 +567,16 @@ func (p *parsed) expr(k string, v json.RawMessage) {
|
||||
Port json.RawMessage `json:"port"`
|
||||
}
|
||||
json.Unmarshal(v, &x)
|
||||
var lookup struct {
|
||||
Map *struct {
|
||||
Key json.RawMessage `json:"key"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
} `json:"map"`
|
||||
}
|
||||
if k == "dnat" && json.Unmarshal(x.Addr, &lookup) == nil && lookup.Map != nil && len(x.Port) == 0 {
|
||||
p.mapUse = &mapUse{kind: "dnat", key: lookup.Map.Key, data: lookup.Map.Data}
|
||||
return
|
||||
}
|
||||
addr, port := values(x.Addr), values(x.Port)
|
||||
if len(addr) != 1 || len(port) > 1 {
|
||||
p.bad(k + " to a range or map")
|
||||
@@ -572,6 +613,13 @@ func (p *parsed) expr(k string, v json.RawMessage) {
|
||||
}
|
||||
case "flow":
|
||||
p.flowAdd = true
|
||||
case "vmap":
|
||||
var x struct {
|
||||
Key json.RawMessage `json:"key"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
json.Unmarshal(v, &x)
|
||||
p.mapUse = &mapUse{kind: "vmap", key: x.Key, data: x.Data}
|
||||
default:
|
||||
p.bad(k)
|
||||
}
|
||||
@@ -740,8 +788,11 @@ func (im *importer) run() {
|
||||
// target chain gets the jump's own matches in front.
|
||||
func (im *importer) flatten(chain string, prefix *parsed, depth int) []*parsed {
|
||||
var out []*parsed
|
||||
var rules []*parsed
|
||||
for _, r := range im.rules[chain] {
|
||||
p := parse(r)
|
||||
rules = append(rules, im.expandMaps(parse(r))...)
|
||||
}
|
||||
for _, p := range rules {
|
||||
if prefix != nil {
|
||||
p = im.merge(prefix, p)
|
||||
}
|
||||
@@ -770,6 +821,115 @@ func (im *importer) flatten(chain string, prefix *parsed, depth int) []*parsed {
|
||||
return out
|
||||
}
|
||||
|
||||
// expandMaps turns a rule that looks something up in a map into one rule
|
||||
// per element, each matching the element's key and doing what its value
|
||||
// says: "tcp dport vmap { 22 : accept, 23 : drop }" becomes "tcp dport 22
|
||||
// accept" and "tcp dport 23 drop"; "dnat ip to tcp dport map @forwards"
|
||||
// one dnat (later a port forward) per element. A lookup finds at most one
|
||||
// element, and their keys don't overlap, so the rules do the same in
|
||||
// order. A map that can't be expanded leaves the rule unsupported.
|
||||
func (im *importer) expandMaps(p *parsed) []*parsed {
|
||||
u := p.mapUse
|
||||
if u == nil {
|
||||
return []*parsed{p}
|
||||
}
|
||||
p.mapUse = nil
|
||||
fail := func(why string) []*parsed {
|
||||
p.bad(why)
|
||||
return []*parsed{p}
|
||||
}
|
||||
var keyObj map[string]json.RawMessage
|
||||
json.Unmarshal(u.key, &keyObj)
|
||||
if keyObj["concat"] != nil {
|
||||
return fail("a map keyed by several fields (" + u.kind + ")")
|
||||
}
|
||||
// The elements: a named map's, or the anonymous one's.
|
||||
var elems []json.RawMessage
|
||||
mapName := ""
|
||||
var ref string
|
||||
if json.Unmarshal(u.data, &ref) == nil && strings.HasPrefix(ref, "@") {
|
||||
m, ok := im.maps[ref[1:]]
|
||||
if !ok {
|
||||
return fail("the map " + ref + ", which isn't in the table")
|
||||
}
|
||||
mapName, elems = m.Name, m.Elem
|
||||
} else {
|
||||
var anon struct {
|
||||
Set []json.RawMessage `json:"set"`
|
||||
}
|
||||
if json.Unmarshal(u.data, &anon) != nil || anon.Set == nil {
|
||||
return fail("a " + u.kind + " map Housecarl can't read")
|
||||
}
|
||||
elems = anon.Set
|
||||
}
|
||||
if len(elems) == 0 {
|
||||
im.note("rule handle %d looks up the empty map %s: left out", p.handle, map[bool]string{true: "@" + mapName, false: "{}"}[mapName != ""])
|
||||
return nil
|
||||
}
|
||||
var out []*parsed
|
||||
for _, e := range elems {
|
||||
var pair []json.RawMessage
|
||||
if json.Unmarshal(e, &pair) != nil || len(pair) != 2 {
|
||||
return fail("a map element Housecarl can't read")
|
||||
}
|
||||
q := *p
|
||||
q.unsupported = append([]string{}, p.unsupported...)
|
||||
q.match(match{Op: "==", Left: u.key, Right: pair[0]})
|
||||
switch u.kind {
|
||||
case "dnat":
|
||||
addr, port := mapTarget(pair[1])
|
||||
if addr == "" {
|
||||
return fail("a dnat map whose values aren't addresses (or address . port)")
|
||||
}
|
||||
q.dnatAddr, q.dnatPort = addr, port
|
||||
if mapName != "" {
|
||||
if k := values(pair[0]); len(k) == 1 {
|
||||
q.name = mapName + "-" + k[0]
|
||||
}
|
||||
}
|
||||
case "vmap":
|
||||
var verdict map[string]json.RawMessage
|
||||
if json.Unmarshal(pair[1], &verdict) != nil || len(verdict) != 1 {
|
||||
return fail("a verdict map value Housecarl can't read")
|
||||
}
|
||||
for k, v := range verdict {
|
||||
q.expr(k, v)
|
||||
}
|
||||
}
|
||||
out = append(out, &q)
|
||||
}
|
||||
if mapName != "" {
|
||||
what := map[string]string{"dnat": "port forward", "vmap": "rule"}[u.kind]
|
||||
if !im.noted["map "+mapName] {
|
||||
im.noted["map "+mapName] = true
|
||||
if len(elems) == 1 {
|
||||
im.note("map %s: its element became a %s", mapName, what)
|
||||
} else {
|
||||
im.note("map %s: its %d elements became %d %ss, one each", mapName, len(elems), len(elems), what)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// mapTarget reads a dnat map's value: an address, or address . port.
|
||||
func mapTarget(raw json.RawMessage) (addr, port string) {
|
||||
var obj struct {
|
||||
Concat []json.RawMessage `json:"concat"`
|
||||
}
|
||||
if json.Unmarshal(raw, &obj) == nil && len(obj.Concat) == 2 {
|
||||
a, b := values(obj.Concat[0]), values(obj.Concat[1])
|
||||
if len(a) == 1 && len(b) == 1 {
|
||||
return a[0], b[0]
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
if a := values(raw); len(a) == 1 {
|
||||
return a[0], ""
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// hasMatch reports whether p matches more than the jump's own matches.
|
||||
func hasMatch(p, prefix *parsed) bool {
|
||||
return len(p.dports) != len(prefix.dports) || len(p.src) != len(prefix.src) || len(p.dst) != len(prefix.dst) ||
|
||||
@@ -1048,7 +1208,12 @@ func (im *importer) rule(chain string, p *parsed) {
|
||||
case "accept", "drop", "reject":
|
||||
r.Action = p.verdict
|
||||
case "":
|
||||
if !p.log {
|
||||
switch {
|
||||
case len(p.unsupported) > 0:
|
||||
// The verdict may be in what couldn't be read (a verdict map).
|
||||
im.rawRule(r, p)
|
||||
return
|
||||
case !p.log:
|
||||
im.note("a %s rule with no verdict (handle %d) was left out", chain, p.handle)
|
||||
return
|
||||
}
|
||||
@@ -1065,6 +1230,8 @@ func (im *importer) rule(chain string, p *parsed) {
|
||||
im.rawRule(r, p)
|
||||
}
|
||||
|
||||
var mapRefRe = regexp.MustCompile(`@([A-Za-z0-9_]+)`)
|
||||
|
||||
func (im *importer) rawRule(r *model.Rule, p *parsed) {
|
||||
text, ok := im.text[p.handle]
|
||||
if !ok {
|
||||
@@ -1075,6 +1242,15 @@ func (im *importer) rawRule(r *model.Rule, p *parsed) {
|
||||
if i := strings.LastIndex(text, ` comment "`); i >= 0 {
|
||||
text = strings.TrimSpace(text[:i])
|
||||
}
|
||||
// A named map isn't carried over: a raw rule naming it would make
|
||||
// nftables refuse the whole ruleset.
|
||||
for _, ref := range mapRefRe.FindAllStringSubmatch(text, -1) {
|
||||
if _, ok := im.maps[ref[1]]; ok {
|
||||
im.note("rule handle %d of chain %s uses the map @%s, which couldn't be expanded (%s): left out, add it by hand: %s",
|
||||
p.handle, r.Chain, ref[1], strings.Join(p.unsupported, ", "), text)
|
||||
return
|
||||
}
|
||||
}
|
||||
if r.Action == "" {
|
||||
r.Action = model.ActionAccept
|
||||
}
|
||||
@@ -1100,6 +1276,9 @@ func (im *importer) dstnat(p *parsed) {
|
||||
break
|
||||
}
|
||||
f := &model.PortForward{Protocols: []string{p.proto}, Ports: ports, To: p.dnatAddr, ToPort: p.dnatPort, Comment: p.comment, Name: forwardName(p.comment)}
|
||||
if p.name != "" {
|
||||
f.Name = strings.ToLower(nameRe.ReplaceAllString(p.name, "-"))
|
||||
}
|
||||
if f.ToPort == ports {
|
||||
f.ToPort = ""
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -173,3 +174,68 @@ func TestImportedRulesetPassesNft(t *testing.T) {
|
||||
t.Errorf("nft -c: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestImportMaps: dnat maps become port forwards and verdict maps rules,
|
||||
// one per element (testdata/maps.nft); a map that can't be expanded is
|
||||
// left out with a note, never kept as a raw rule naming it.
|
||||
func TestImportMaps(t *testing.T) {
|
||||
js, _ := os.ReadFile("testdata/maps.json")
|
||||
txt, _ := os.ReadFile("testdata/maps.txt")
|
||||
res, err := Import(Input{JSON: js, Text: string(txt)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var fwds []string
|
||||
for _, f := range res.State.Forwards {
|
||||
fwds = append(fwds, fmt.Sprintf("%s %s/%s->%s:%s", f.Name, strings.Join(f.Protocols, "+"), f.Ports, f.To, f.ToPort))
|
||||
}
|
||||
var rules []string
|
||||
for _, r := range res.State.Rules {
|
||||
rules = append(rules, fmt.Sprintf("%s in=%s from=%s %s %s%s", r.Chain, strings.Join(r.InZones, ","), strings.Join(r.Sources, ","),
|
||||
strings.Join(r.Services, ","), r.Action, r.Raw))
|
||||
}
|
||||
want := []string{
|
||||
"portforwards-80 tcp/80->192.168.1.50:8080",
|
||||
"portforwards-443 tcp/443->192.168.1.50:",
|
||||
"hosts-2222 tcp/2222->192.168.1.60:",
|
||||
" udp/5000->192.168.1.70:5001",
|
||||
" udp/6000->192.168.1.71:",
|
||||
}
|
||||
if got := strings.Join(fwds, "\n"); got != strings.Join(want, "\n") {
|
||||
t.Errorf("forwards:\n%s\nwant:\n%s", got, strings.Join(want, "\n"))
|
||||
}
|
||||
// In order; the jump in the verdict map is inlined.
|
||||
want = []string{
|
||||
"input in= from= accept",
|
||||
"input in=lan from= tcp/22 accept",
|
||||
"input in=lan from= tcp/23 drop",
|
||||
"input in=lan from=192.168.1.0/24 tcp/80 accept",
|
||||
"input in=wan from= udp/53 accept",
|
||||
"input in=wan from= udp/123 drop",
|
||||
"input in= from=10.0.0.0/8 accept",
|
||||
"input in= from=192.0.2.1 drop",
|
||||
}
|
||||
if got := strings.Join(rules, "\n"); got != strings.Join(want, "\n") {
|
||||
t.Errorf("rules:\n%s\nwant:\n%s", got, strings.Join(want, "\n"))
|
||||
}
|
||||
notes := strings.Join(res.Notes, "\n")
|
||||
for _, n := range []string{"map portforwards: its 2 elements became 2 port forwards", "map hosts: its element became a port forward",
|
||||
"uses the map @pairs, which couldn't be expanded"} {
|
||||
if !strings.Contains(notes, n) {
|
||||
t.Errorf("note %q missing:\n%s", n, notes)
|
||||
}
|
||||
}
|
||||
if len(res.Raw) > 0 {
|
||||
t.Errorf("raw rules: %v", res.Raw)
|
||||
}
|
||||
if ps := validate.Errors(validate.Check(res.State, nil)); len(ps) > 0 {
|
||||
t.Errorf("invalid: %+v", ps)
|
||||
}
|
||||
if cmd := strings.Fields(os.Getenv("HOUSECARL_NFT")); len(cmd) > 0 {
|
||||
c := exec.Command(cmd[0], append(cmd[1:], "-c", "-f", "/dev/stdin")...)
|
||||
c.Stdin = strings.NewReader(render.Render(res.State, render.Options{}).Text)
|
||||
if out, err := c.CombinedOutput(); err != nil {
|
||||
t.Errorf("nft -c: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nftables": [{"metainfo": {"version": "1.1.3", "release_name": "Commodore Bullmoose #4", "json_schema_version": 1}}, {"table": {"family": "inet", "name": "main", "handle": 3}}, {"chain": {"family": "inet", "table": "main", "name": "web", "handle": 1}}, {"chain": {"family": "inet", "table": "main", "name": "prerouting", "handle": 2, "type": "nat", "hook": "prerouting", "prio": -100, "policy": "accept"}}, {"chain": {"family": "inet", "table": "main", "name": "input", "handle": 3, "type": "filter", "hook": "input", "prio": 0, "policy": "drop"}}, {"map": {"family": "inet", "name": "portforwards", "table": "main", "type": "inet_service", "handle": 4, "map": ["ipv4_addr", "inet_service"], "elem": [[80, {"concat": ["192.168.1.50", 8080]}], [443, {"concat": ["192.168.1.50", 443]}]]}}, {"map": {"family": "inet", "name": "hosts", "table": "main", "type": "inet_service", "handle": 5, "map": "ipv4_addr", "elem": [[2222, "192.168.1.60"]]}}, {"map": {"family": "inet", "name": "lan_services", "table": "main", "type": "inet_service", "handle": 6, "map": "verdict", "elem": [[22, {"accept": null}], [23, {"drop": null}], [80, {"jump": {"target": "web"}}]]}}, {"map": {"family": "inet", "name": "pairs", "table": "main", "type": ["ipv4_addr", "inet_service"], "handle": 7, "map": "verdict", "elem": [[{"concat": ["192.168.1.9", 22]}, {"accept": null}]]}}, {"rule": {"family": "inet", "table": "main", "chain": "web", "handle": 8, "expr": [{"match": {"op": "==", "left": {"payload": {"protocol": "ip", "field": "saddr"}}, "right": {"prefix": {"addr": "192.168.1.0", "len": 24}}}}, {"accept": null}]}}, {"rule": {"family": "inet", "table": "main", "chain": "prerouting", "handle": 9, "comment": "Port forwards", "expr": [{"match": {"op": "==", "left": {"meta": {"key": "iifname"}}, "right": "wan"}}, {"dnat": {"family": "ip", "addr": {"map": {"key": {"payload": {"protocol": "tcp", "field": "dport"}}, "data": "@portforwards"}}}}]}}, {"rule": {"family": "inet", "table": "main", "chain": "prerouting", "handle": 10, "expr": [{"match": {"op": "==", "left": {"meta": {"key": "iifname"}}, "right": "wan"}}, {"dnat": {"family": "ip", "addr": {"map": {"key": {"payload": {"protocol": "tcp", "field": "dport"}}, "data": "@hosts"}}}}]}}, {"rule": {"family": "inet", "table": "main", "chain": "prerouting", "handle": 12, "expr": [{"match": {"op": "==", "left": {"meta": {"key": "iifname"}}, "right": "wan"}}, {"dnat": {"family": "ip", "addr": {"map": {"key": {"payload": {"protocol": "udp", "field": "dport"}}, "data": {"set": [[5000, {"concat": ["192.168.1.70", 5001]}], [6000, {"concat": ["192.168.1.71", 6000]}]]}}}}}]}}, {"rule": {"family": "inet", "table": "main", "chain": "input", "handle": 13, "expr": [{"match": {"op": "in", "left": {"ct": {"key": "state"}}, "right": ["established", "related"]}}, {"accept": null}]}}, {"rule": {"family": "inet", "table": "main", "chain": "input", "handle": 14, "expr": [{"match": {"op": "==", "left": {"meta": {"key": "iifname"}}, "right": "lan"}}, {"vmap": {"key": {"payload": {"protocol": "tcp", "field": "dport"}}, "data": "@lan_services"}}]}}, {"rule": {"family": "inet", "table": "main", "chain": "input", "handle": 16, "expr": [{"match": {"op": "==", "left": {"meta": {"key": "iifname"}}, "right": "wan"}}, {"vmap": {"key": {"payload": {"protocol": "udp", "field": "dport"}}, "data": {"set": [[53, {"accept": null}], [123, {"drop": null}]]}}}]}}, {"rule": {"family": "inet", "table": "main", "chain": "input", "handle": 18, "expr": [{"vmap": {"key": {"payload": {"protocol": "ip", "field": "saddr"}}, "data": {"set": [[{"prefix": {"addr": "10.0.0.0", "len": 8}}, {"accept": null}], ["192.0.2.1", {"drop": null}]]}}}]}}, {"rule": {"family": "inet", "table": "main", "chain": "input", "handle": 19, "expr": [{"vmap": {"key": {"concat": [{"payload": {"protocol": "ip", "field": "saddr"}}, {"payload": {"protocol": "tcp", "field": "dport"}}]}, "data": "@pairs"}}]}}]}
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
# Maps and verdict maps, as found in hand-written rulesets (importer_test.go).
|
||||
table inet main {
|
||||
map portforwards {
|
||||
type inet_service : ipv4_addr . inet_service
|
||||
elements = { 443 : 192.168.1.50 . 443, 80 : 192.168.1.50 . 8080 }
|
||||
}
|
||||
map hosts {
|
||||
type inet_service : ipv4_addr
|
||||
elements = { 2222 : 192.168.1.60 }
|
||||
}
|
||||
map lan_services {
|
||||
type inet_service : verdict
|
||||
elements = { 22 : accept, 23 : drop, 80 : jump web }
|
||||
}
|
||||
map pairs {
|
||||
type ipv4_addr . inet_service : verdict
|
||||
elements = { 192.168.1.9 . 22 : accept }
|
||||
}
|
||||
chain web {
|
||||
ip saddr 192.168.1.0/24 accept
|
||||
}
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority dstnat; policy accept;
|
||||
iifname "wan" dnat ip to tcp dport map @portforwards comment "Port forwards"
|
||||
iifname "wan" dnat ip to tcp dport map @hosts
|
||||
iifname "wan" dnat ip to udp dport map { 5000 : 192.168.1.70 . 5001, 6000 : 192.168.1.71 . 6000 }
|
||||
}
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
ct state established,related accept
|
||||
iifname "lan" tcp dport vmap @lan_services
|
||||
iifname "wan" udp dport vmap { 53 : accept, 123 : drop }
|
||||
ip saddr vmap { 10.0.0.0/8 : accept, 192.0.2.1 : drop }
|
||||
ip saddr . tcp dport vmap @pairs
|
||||
}
|
||||
}
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
table inet main { # handle 3
|
||||
map portforwards { # handle 4
|
||||
type inet_service : ipv4_addr . inet_service
|
||||
elements = { 80 : 192.168.1.50 . 8080,
|
||||
443 : 192.168.1.50 . 443 }
|
||||
}
|
||||
|
||||
map hosts { # handle 5
|
||||
type inet_service : ipv4_addr
|
||||
elements = { 2222 : 192.168.1.60 }
|
||||
}
|
||||
|
||||
map lan_services { # handle 6
|
||||
type inet_service : verdict
|
||||
elements = { 22 : accept,
|
||||
23 : drop,
|
||||
80 : jump web }
|
||||
}
|
||||
|
||||
map pairs { # handle 7
|
||||
type ipv4_addr . inet_service : verdict
|
||||
elements = { 192.168.1.9 . 22 : accept }
|
||||
}
|
||||
|
||||
chain web { # handle 1
|
||||
ip saddr 192.168.1.0/24 accept # handle 8
|
||||
}
|
||||
|
||||
chain prerouting { # handle 2
|
||||
type nat hook prerouting priority dstnat; policy accept;
|
||||
iifname "wan" dnat ip to tcp dport map @portforwards comment "Port forwards" # handle 9
|
||||
iifname "wan" dnat ip to tcp dport map @hosts # handle 10
|
||||
iifname "wan" dnat ip to udp dport map { 5000 : 192.168.1.70 . 5001, 6000 : 192.168.1.71 . 6000 } # handle 12
|
||||
}
|
||||
|
||||
chain input { # handle 3
|
||||
type filter hook input priority filter; policy drop;
|
||||
ct state established,related accept # handle 13
|
||||
iifname "lan" tcp dport vmap @lan_services # handle 14
|
||||
iifname "wan" udp dport vmap { 53 : accept, 123 : drop } # handle 16
|
||||
ip saddr vmap { 10.0.0.0/8 : accept, 192.0.2.1 : drop } # handle 18
|
||||
ip saddr . tcp dport vmap @pairs # handle 19
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user