2

Using kernel 2.6.x

Note: In a previous question, I tried to accomplish this with a bash array. On this OS, non-sh shells are installed as Entware-NG packages and can't be used since they load after the script runs.

Goal: Script the creation of iptables rules using sh (not bash, zsh, etc.) with the following format. For each interface in $NAME ...

  • Create a rule for it's source network IP address from the corresponding position in $NETID. The first value in $NAME has a source network address built from the first value in $NETID. For example, -i eth1 corresponds with -src 192.168.10.0/24 and -i eth2 corresponds with 192.168.20.0/24.
  • Build the destination IP address for the values in $NETID not used in the source network address. For example, -src 192.168.10.0/24 -dst 192.168.20.0/24.
  • Build each rule with one destination network address. Do not specify multiple addresses for the -dst value.

Desired Result:

iptables -I FORWARD -i eth1 -src 192.168.10.0/24 -dst 192.168.20.0/24
iptables -I FORWARD -i eth1 -src 192.168.10.0/24 -dst 192.168.30.0/24
iptables -I FORWARD -i eth2 -src 192.168.20.0/24 -dst 192.168.10.0/24
iptables -I FORWARD -i eth2 -src 192.168.20.0/24 -dst 192.168.30.0/24
iptables -I FORWARD -i eth3 -src 192.168.30.0/24 -dst 192.168.10.0/24
iptables -I FORWARD -i eth3 -src 192.168.30.0/24 -dst 192.168.20.0/24

Problem: The script below does not create a 1:1 relationship with the interface name and -src value. For example, eth1 and 192.168.10.0/24, eth2 and 192.168.20.0/24, etc.


#!/bin/sh
NETID="10 20 30"
NAME="eth1 eth2 eth3"



for i in $NETID; do
   sarg="192.168.$i.0/24"
   darg=""

   for j in $NETID; do
      [ "$i" -eq "$j" ] && continue
      darg="192.168.$j.0/24"
      for k in $NAME; do
         echo "iptables -I FORWARD -i $k -s $sarg -d $darg -j DROP"
      done
   done
done
  • 1
    -eq is for numerical comparisons. Use = instead. [ "$i" = "$j" ] && continue – Wildcard Sep 8 '17 at 2:01
  • Wait, what? How do you tie NAME to src? (You don't.) That code won't ever get your desired result. – Wildcard Sep 8 '17 at 2:06
  • 2
    IIUC, the question boils down to "how do I emulate Bash arrays with a Posix shell?", i.e. how to translate NAME=([10]=eth1 [20]=eth2 [30]=eth3) and ${NAME[$NETID]}? – xhienne Sep 8 '17 at 13:46
  • @xhienne - I only have access to what sh supports. – uihdff Sep 8 '17 at 14:06
  • 1
    @uihdff Yes, sh is what I call a Posix shell. My summary is to avoid misled answers. What you want to achieve is get the name from the netid, that is the equivalent of ${NAME[$NETID]}, but with a mere sh. – xhienne Sep 8 '17 at 14:09
1

You main problem is that you have a 1:1 relationship between the interface names (eth1, eth2,...) and the associated networks (10.x, 20.x,...) but there is nothing in your code that explicitly establishes that 1:1 relationship.

You must somewhere bind ethN to 192.168.N.x. You can do it with a single NETS variable which would contain a list of interface:N pairs. See my code below:

#!/bin/sh
NETS="eth1:10 eth2:20 eth3:30"

for net in $NETS; do
    k="${net%:*}"          # Yields the name of the interface
    i="${net#*:}"          # Yields the number bound to the interface
    sarg="192.168.$i.0/24"

    for net in $NETS; do
        j="${net#*:}"      # Yields the number bound to the interface
        [ "$i" -eq "$j" ] && continue
        darg="192.168.$j.0/24"
        echo "iptables -I FORWARD -i $k -s $sarg -d $darg -j DROP"
    done
done

Notes:

  • I removed the for k in $NAME loop entirely since it seems to contradict what you want to achieve.
  • You could even put the network in the definition of NETS and read sarg and darg directly from it. Something like:

    NETS="eth1:192.168.10.0/24 eth2:192.168.20.0/24 eth3:192.168.30.0/24"
    

[update] Combined with my answer to your other question, this would give this script:

#!/bin/sh
NETID="10 20 30"
NAME="eth1 eth2 eth3"
NETS=$(set -- $NETID; for iface in $NAME; do echo "$iface:192.168.$1.0/24"; shift; done)

for net in $NETS; do
    iface="${net%:*}"
    sarg="${net#*:}"

    for net in $NETS; do
        darg="${net#*:}"
        [ "$sarg" != "$darg" ] && echo "iptables -I FORWARD -i $k -s $sarg -d $darg -j DROP"
    done
done
  • How could you create a command that builds the $NETS values from $NAME and $NETID ? Using grep and cut ? – uihdff Sep 10 '17 at 11:59
  • 2
    @uihdff OK, I see. Wildcard's answer with awk seems good to me, and it uses the same variables NAME and NETID; you should go with that answer then. If you really prefer a shell script, you should ask another question, stating how NETID and NAME are constructed, and (referring to this answer) how NETS could be constructed in the same way. – xhienne Sep 12 '17 at 22:49
  • 1
    ${net%:*} removes everything after and including ":". Same for {net#*:} but before ":". i, j, and k have the same meaning and content as in your own script. – xhienne Sep 13 '17 at 17:30
  • 1
    @uihdff Note: answer updated with my other answer – xhienne Sep 13 '17 at 20:17
  • 1
    @uihdff I added it following Wildcard's comment to my other answer. It has no significance at all in this context, it's only significant when you don't know in advance what will follow. See my answer there. As for -eq it's dedicated to comparing numbers ("0" -eq "-000" would yield true), whereas = is a strict string comparison. – xhienne Sep 23 '17 at 10:36
3

Using POSIX Awk, and not worrying about the sequence of the output lines:

echo | awk -v 'netids=10 20 30' -v 'names=eth1 eth2 eth3' '
  BEGIN {
    netidcount = split(netids, netid)
    namecount = split(names, name)
    if (netidcount != namecount) {
      print "Error: You must pass the same number of names and netids."
      exit 1
    }
    for (i in name) {
      for (j in name) {
        if (i == j) {
          continue
        }
        printf "iptables -I FORWARD -i %s -src 192.168.%s.0/24 -dst 192.168.%s.0/24 -j DROP\n", name[i], netid[i], netid[j]
      }
    }
  }'

Of course you can vary this; for instance you can put the awk script above in a file and call it with:

echo | awk -v ... -v ... -f script.awk

Or, you can put the netids and names in data.txt, perhaps formatted like so:

eth1 10
eth2 20
eth3 30

And write the Awk script file script.awk like so:

#!/bin/awk
NF != 2 {
  error = 1
  print "Error: Each line of input must contain one interface name and one number."
  exit
}
(($1 in nameseen) || ($2 in netidseen)) {
  error = 1
  print "Error: Each interface name and network id in input must be unique."
  exit
}
{
  name[NR] = $1
  netid[NR] = $2
  nameseen[$1]
  netidseen[$2]
}
END {
  if (error) {
    exit error
  }
  for (i in name) {
    for (j in name) {
      if (i == j) {
        continue
      }
      printf "iptables -I FORWARD -i %s -src 192.168.%s.0/24 -dst 192.168.%s.0/24 -j DROP\n", name[i], netid[i], netid[j]
    }
  }
}

And then call it like so:

awk -f script.awk data.txt

Lots of choices.

I would probably have data.txt look like:

eth1 192.168.10.0/24
eth2 192.168.20.0/24
eth3 192.168.30.0/24

Modifying the script to work with this data format is left as an exercise to the reader. ;)


(Note: if you use this code, please do remember to include a link to this answer as attribution.)

  • Thank you for providing such a detailed solution. Is the problem really that complicated ? – uihdff Sep 8 '17 at 16:20
  • 1
    @uihdff, the code is almost Production-ready. It's error handling that makes it longer. Most solutions on Stack Exchange show the basic concept and leave error handling out. (I say "almost" because if this script is piped to sh or something, you'll get errors because Error: isn't a valid shell command. And because I think a better and simpler solution may be possible by digging in to the iptables man page.) – Wildcard Sep 8 '17 at 16:31
  • 1
    @uihdff, for instance, did you know you can specify multiple destination addresses with -dst? Or for that matter, you can use an inverted destination flag ! -dst if you want to drop everything that doesn't go to the specified address. (In which case you could just use e.g. -src 192.168.10.0/24 \! -dst 192.168.10.0/24 -j DROP.) – Wildcard Sep 8 '17 at 16:39
2

POSIX sh version:

for n in 1 2 3; do
  src="$n"0
  interface="eth$n"
  for dest in 10 20 30; do
    [ "$dest" = "$src" ] && continue
    printf 'iptables -I FORWARD -i %s -src %s -dst %s -j DROP\n' "$interface" "192.168.$src.0/24" "192.168.$dest.0/24"
  done
done

I've made some assumptions about how you're tying interface to source for your rules. But this does produce the desired output you showed.

  • Each interface in $NAME has its own network address based on the corresponding value in $NETID. How would you write this using only the $NETID and $NAME variables instead of "1 2 3" ? – uihdff Sep 8 '17 at 13:32
  • 1
    @uihdff that's almost impossible with POSIX sh without redundant data. It can be done, hackily, but better would be to use Awk. – Wildcard Sep 8 '17 at 15:35
  • How would you use awk to accomplish this ? – uihdff Sep 8 '17 at 15:51
  • 1
    @uihdff, see new answer. – Wildcard Sep 8 '17 at 16:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.