namib-project / nftables-rs

Safe abstraction for nftables JSON API (libnftables-json).
https://crates.io/crates/nftables
Apache License 2.0
32 stars 15 forks source link

json schema is missing synproxy support #15

Closed Luap99 closed 1 month ago

Luap99 commented 8 months ago

some info about synproxy can be found here https://wiki.nftables.org/wiki-nftables/index.php/Synproxy

The field is not documented in the libnftables-json(5) page

some example output for named synproxy

# nft list ruleset
table ip foo {
    synproxy https-synproxy {
        mss 1460
        wscale 7
        timestamp sack-perm
    }

    synproxy other-synproxy {
        mss 1460
        wscale 5
    }

    chain pre {
        type filter hook prerouting priority raw; policy accept;
        tcp dport 8888 tcp flags syn notrack
    }

    chain bar {
        type filter hook forward priority filter; policy accept;
        ct state invalid,untracked synproxy name ip saddr map { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" }
    }
}

# nft -j list ruleset | jq
{
  "nftables": [
    {
      "metainfo": {
        "version": "1.0.7",
        "release_name": "Old Doc Yak",
        "json_schema_version": 1
      }
    },
    {
      "table": {
        "family": "ip",
        "name": "foo",
        "handle": 2
      }
    },
    {
      "synproxy": {
        "family": "ip",
        "name": "https-synproxy",
        "table": "foo",
        "handle": 3,
        "mss": 1460,
        "wscale": 7,
        "flags": [
          "timestamp",
          "sack-perm"
        ]
      }
    },
    {
      "synproxy": {
        "family": "ip",
        "name": "other-synproxy",
        "table": "foo",
        "handle": 4,
        "mss": 1460,
        "wscale": 5
      }
    },
    {
      "chain": {
        "family": "ip",
        "table": "foo",
        "name": "pre",
        "handle": 1,
        "type": "filter",
        "hook": "prerouting",
        "prio": -300,
        "policy": "accept"
      }
    },
    {
      "chain": {
        "family": "ip",
        "table": "foo",
        "name": "bar",
        "handle": 2,
        "type": "filter",
        "hook": "forward",
        "prio": 0,
        "policy": "accept"
      }
    },
    {
      "rule": {
        "family": "ip",
        "table": "foo",
        "chain": "pre",
        "handle": 5,
        "expr": [
          {
            "match": {
              "op": "==",
              "left": {
                "payload": {
                  "protocol": "tcp",
                  "field": "dport"
                }
              },
              "right": 8888
            }
          },
          {
            "match": {
              "op": "in",
              "left": {
                "payload": {
                  "protocol": "tcp",
                  "field": "flags"
                }
              },
              "right": "syn"
            }
          },
          {
            "notrack": null
          }
        ]
      }
    },
    {
      "rule": {
        "family": "ip",
        "table": "foo",
        "chain": "bar",
        "handle": 7,
        "expr": [
          {
            "match": {
              "op": "in",
              "left": {
                "ct": {
                  "key": "state"
                }
              },
              "right": [
                "invalid",
                "untracked"
              ]
            }
          },
          {
            "synproxy": {
              "map": {
                "key": {
                  "payload": {
                    "protocol": "ip",
                    "field": "saddr"
                  }
                },
                "data": {
                  "set": [
                    [
                      {
                        "prefix": {
                          "addr": "192.168.1.0",
                          "len": 24
                        }
                      },
                      "https-synproxy"
                    ],
                    [
                      {
                        "prefix": {
                          "addr": "192.168.2.0",
                          "len": 24
                        }
                      },
                      "other-synproxy"
                    ]
                  ]
                }
              }
            }
          }
        ]
      }
    }
  ]
}

and for anonymous synproxy

# nft list ruleset
table ip anon_synproxy_demo {
    chain PRE {
        type filter hook prerouting priority raw; policy accept;
        tcp dport 8888 tcp flags syn notrack
    }

    chain IN {
        type filter hook input priority filter; policy accept;
        tcp dport 8888 ct state invalid,untracked synproxy mss 1460 wscale 7 timestamp sack-perm
        ct state invalid drop
    }
}
root@pholzing-fedora:/home/pholzing/CODE/nftables-rs# nft -j list ruleset | jq
{
  "nftables": [
    {
      "metainfo": {
        "version": "1.0.7",
        "release_name": "Old Doc Yak",
        "json_schema_version": 1
      }
    },
    {
      "table": {
        "family": "ip",
        "name": "anon_synproxy_demo",
        "handle": 3
      }
    },
    {
      "chain": {
        "family": "ip",
        "table": "anon_synproxy_demo",
        "name": "PRE",
        "handle": 1,
        "type": "filter",
        "hook": "prerouting",
        "prio": -300,
        "policy": "accept"
      }
    },
    {
      "chain": {
        "family": "ip",
        "table": "anon_synproxy_demo",
        "name": "IN",
        "handle": 2,
        "type": "filter",
        "hook": "input",
        "prio": 0,
        "policy": "accept"
      }
    },
    {
      "rule": {
        "family": "ip",
        "table": "anon_synproxy_demo",
        "chain": "PRE",
        "handle": 3,
        "expr": [
          {
            "match": {
              "op": "==",
              "left": {
                "payload": {
                  "protocol": "tcp",
                  "field": "dport"
                }
              },
              "right": 8888
            }
          },
          {
            "match": {
              "op": "in",
              "left": {
                "payload": {
                  "protocol": "tcp",
                  "field": "flags"
                }
              },
              "right": "syn"
            }
          },
          {
            "notrack": null
          }
        ]
      }
    },
    {
      "rule": {
        "family": "ip",
        "table": "anon_synproxy_demo",
        "chain": "IN",
        "handle": 4,
        "expr": [
          {
            "match": {
              "op": "==",
              "left": {
                "payload": {
                  "protocol": "tcp",
                  "field": "dport"
                }
              },
              "right": 8888
            }
          },
          {
            "match": {
              "op": "in",
              "left": {
                "ct": {
                  "key": "state"
                }
              },
              "right": [
                "invalid",
                "untracked"
              ]
            }
          },
          {
            "synproxy": {
              "mss": 1460,
              "wscale": 7,
              "flags": [
                "timestamp",
                "sack-perm"
              ]
            }
          }
        ]
      }
    },
    {
      "rule": {
        "family": "ip",
        "table": "anon_synproxy_demo",
        "chain": "IN",
        "handle": 5,
        "expr": [
          {
            "match": {
              "op": "in",
              "left": {
                "ct": {
                  "key": "state"
                }
              },
              "right": "invalid"
            }
          },
          {
            "drop": null
          }
        ]
      }
    }
  ]
}

found originally in https://github.com/containers/netavark/issues/942

jwhb commented 8 months ago

Thanks for reporting.

Implementing this will require a little bit of reverse engineering of the JSON API as it's not documented. We can use an nft ruleset that presents all valid forms of the synproxy structure, then:

That will be the reference to work on the schema/expr implementations.

I'll start working on this soon™ unless somebody else jumps in. If there is high demand for this feature, please let me know.

Luap99 commented 8 months ago

Yeah I think there more options in the json that are not documented in libnftables-json(5), there are more options documented in nft(8) but of course they do not show how that looks in the json format. So I agree that we need to reverse engineering the format, that is why I included the example outputs above.

This isn't a priority at all for us (netavark) as we have no need for this feature, the problem for us is/was that unknown rules will break the json deserialization. However with https://github.com/namib-project/nftables-rs/pull/17 we now have the option to only list rules for our table "netavark" which means that should no longer happen as the rules there will only be added by this lib so it should be theoretically impossible to hit such a case.

jwhb commented 1 month ago

@Luap99: The SynProxy implementation will be released as v0.5.0 as soon as all PRs are merged.