pythonnftables

Add nftables map element using libnftables-json API from python


I am trying to dynamically add a map element using the nftables JSON API from python. In my firewall I have the following map in the router table in the ip family:

map port_forwards {
    type inet_service: ipv4_addr . inet_service;
}

Here is a minimal example of what I am trying to do:

import nftables

nft_cmd = {"nftables": [
    { "add": { "element":{
        "family": "ip",
        "table": "router",
        "name": "port_forwards",
        "elem": { "map": {
            "key": "80",
            "data": "172.16.0.1 . 80"
        }}
    }}}
]}

nft = nftables.Nftables()
nft.json_validate(nft_cmd)
rc, _output, error = nft.json_cmd(nft_cmd)
if rc != 0:
    raise RuntimeError(f"Error running nftables command: {error}")

This results in the following error:

RuntimeError: Error running nftables command: internal:0:0-0: Error: Unexpected JSON type object for immediate value.

internal:0:0-0: Error: Invalid set.

internal:0:0-0: Error: Parsing command array at index 0 failed.

I assume I am mis-understanding the spec somehow (https://manpages.debian.org/unstable/libnftables1/libnftables-json.5.en.html), but I can't figure out the correct usage.

UPDATE: I have discovered nft can echo your command in json format. This is the command:

sudo nft -e -j add element ip router port_forwards '{80 : 172.16.0.1 . 8080 }'

and the response pretty-printed:

{"nftables": [
    {"add": {"element": {
        "family": "ip",
        "table": "router",
        "name": "port_forwards",
        "elem": {"set": [[
            80,
            {"concat": ["172.16.0.1", 8080]}
        ]]}
    }}}
]}

Unfortunately copying this into the above python code still results in the same error


Solution

  • It turns out that the "elem" property takes the array directly instead of being wrapped in a "set" object. This was hinted at by the error:

    Unexpected JSON type object for immediate value.
    

    The working code is shown below:

    import nftables
    
    nft_cmd = {"nftables": [
        { "add": { "element":{
            "family": "ip",
            "table": "router",
            "name": "port_forwards",
            "elem": [[
                80,
                {"concat": ["172.16.0.1", 8080]}
            ]]
        }}}
    ]}
    
    
    nft = nftables.Nftables()
    nft.json_validate(nft_cmd)
    rc, _output, error = nft.json_cmd(nft_cmd)
    if rc != 0:
        raise RuntimeError(f"Error running nftables command: {error}")