skip to Main Content

I am writing a program that takes a snapshot of iptable rules so that they can be restored at a later point. I don’t want to write the rules to a file; I store them as a string in my code.

This worked perfectly making a call out to iptables-save and capturing the output into a string and then calling iptables-restore combined with bash process substitution to feed the rules string back into iptables e.g. iptables-restore <(echo "<rules here>") (have to change this later though to use pipes since Ubuntu won’t support that)

I encountered a case where there was a log-prefix option with a space on one of the rules that has caused the restore step to break. For example:

[root@host ~]# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
LOG        all  --  anywhere             anywhere             limit: avg 3/min burst 10 LOG level warning prefix "[UFW BLOCK] "

and the output of iptables-save:

[root@host ~]# iptables-save
# Generated by iptables-save v1.8.8 (nf_tables) on Wed May  3 21:45:25 2023
*filter
:INPUT ACCEPT [101:5876]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [84:34334]
-A OUTPUT -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW BLOCK] "
COMMIT
# Completed on Wed May  3 21:45:25 2023

This is the command run by the code:

/bin/sh -c /sbin/iptables-restore <(echo "# Generated by iptables-save v1.8.8 (nf_tables) on Wed May  3 21:45:25 2023
*filter
:INPUT ACCEPT [1936:94741]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [612:2005831]
-A OUTPUT -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW BLOCK] "
COMMIT
# Completed on Wed May  3 21:45:25 2023
")
Bad argument `BLOCK]'
Error occurred at line: 6
Try `iptables-restore -h' or 'iptables-restore --help' for more information.

If I remove the space between UFW and BLOCK it works ok.

I’ve tried escaping the quotation marks in the string too but still the same issue.

Is there some trick to get this to work. Also, I don’t have any control of what could be in the rules and they need to be replaced exactly as taken from the snapshot so preprocessing the string before calling restore wouldn’t work for me.

2

Answers


  1. Chosen as BEST ANSWER

    So it seems after bouncing the issue off a few people in the office I actually have it working. The solution of escaping the quotations was correct e.g. "[UFW BLOCK] ". I had thought it wasn't working because I was monitoring the output of the command:

    /bin/sh -c /sbin/iptables-restore <(echo "# Generated by iptables-save v1.8.8 (nf_tables) on Wed May  3 22:15:05 2023
    *filter
    :INPUT ACCEPT [258:20165]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [210:104923]
    -A OUTPUT -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW BLOCK] "
    COMMIT
    # Completed on Wed May  3 22:15:05 2023
    ")
    

    which has the escape characters present but when running iptables list I see they are not there:

    [root@host ~]# iptables -L
    Chain INPUT (policy ACCEPT)
    target     prot opt source               destination         
    
    Chain FORWARD (policy ACCEPT)
    target     prot opt source               destination         
    
    Chain OUTPUT (policy ACCEPT)
    target     prot opt source               destination         
    LOG        all  --  anywhere             anywhere             limit: avg 3/min burst 10 LOG level warning prefix "[UFW BLOCK] "
    

    Apologies for the dumb moment doh!


  2. You did not show any C++ code, but instead of relying on fragile bash argument handling you could use a pipe and use the /dev/fd/X syntax:

    int pipefd[2];
    int ret = pipe(pipefd);
    if (ret != 0) { /* handle error */ }
    pid_t pid = fork();
    if (pid != 0) {
      /* parent */
      close(pipefd[0]);
      write(pipefd[1], stored_iptables.c_str(), stored_iptables.length());
      int wstatus = 0;
      waitpid(pid, &wstatus, 0);
    } else {
      /* child */
      close(pipefd[1]);
      std::string path = std::string{"/dev/fd/"} + std::to_string(pipefd[0]);
      execl("/sbin/iptables-restore", "/sbin/iptables-restore", path.c_str(), NULL);
    }
    

    To simplify further, you could use mkstemp to generate an anonymous temporary file, dump data into it, and again pass /dev/fd/X to iptables-restore.

    EDIT: the mkstemp solution (equally untested):

    int fd = mkstemp("iptables.XXXXXX");
    write(fd, stored_iptables.c_str(), stored_iptables.length());
    lseek(fd, 0, SEEK_SET);
    std::string path = std::string{"/dev/fd/"} + std::to_string(pipefd[0]);
    execl("/sbin/iptables-restore", "/sbin/iptables-restore", path.c_str(), NULL);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search