Browse Source

Merge branch 'phensley-iptables-stats'

Casey Callendrello 7 years ago
parent
commit
dad1f86779
2 changed files with 95 additions and 0 deletions
  1. 67 0
      iptables/iptables.go
  2. 28 0
      iptables/iptables_test.go

+ 67 - 0
iptables/iptables.go

@@ -18,6 +18,7 @@ import (
 	"bytes"
 	"fmt"
 	"io"
+	"net"
 	"os/exec"
 	"regexp"
 	"strconv"
@@ -174,6 +175,72 @@ func (ipt *IPTables) ListChains(table string) ([]string, error) {
 	return chains, nil
 }
 
+// Stats lists rules including the byte and packet counts
+func (ipt *IPTables) Stats(table, chain string) ([][]string, error) {
+	args := []string{"-t", table, "-L", chain, "-n", "-v", "-x"}
+	lines, err := ipt.executeList(args)
+	if err != nil {
+		return nil, err
+	}
+
+	appendSubnet := func(addr string) string {
+		if strings.IndexByte(addr, byte('/')) < 0 {
+			if strings.IndexByte(addr, '.') < 0 {
+				return addr + "/128"
+			}
+			return addr + "/32"
+		}
+		return addr
+	}
+
+	ipv6 := ipt.proto == ProtocolIPv6
+
+	rows := [][]string{}
+	for i, line := range lines {
+		// Skip over chain name and field header
+		if i < 2 {
+			continue
+		}
+
+		// Fields:
+		// 0=pkts 1=bytes 2=target 3=prot 4=opt 5=in 6=out 7=source 8=destination 9=options
+		line = strings.TrimSpace(line)
+		fields := strings.Fields(line)
+
+		// The ip6tables verbose output cannot be naively split due to the default "opt"
+		// field containing 2 single spaces.
+		if ipv6 {
+			// Check if field 6 is "opt" or "source" address
+			dest := fields[6]
+			ip, _, _ := net.ParseCIDR(dest)
+			if ip == nil {
+				ip = net.ParseIP(dest)
+			}
+
+			// If we detected a CIDR or IP, the "opt" field is empty.. insert it.
+			if ip != nil {
+				f := []string{}
+				f = append(f, fields[:4]...)
+				f = append(f, "  ") // Empty "opt" field for ip6tables
+				f = append(f, fields[4:]...)
+				fields = f
+			}
+		}
+
+		// Adjust "source" and "destination" to include netmask, to match regular
+		// List output
+		fields[7] = appendSubnet(fields[7])
+		fields[8] = appendSubnet(fields[8])
+
+		// Combine "options" fields 9... into a single space-delimited field.
+		options := fields[9:]
+		fields = fields[:9]
+		fields = append(fields, strings.Join(options, " "))
+		rows = append(rows, fields)
+	}
+	return rows, nil
+}
+
 func (ipt *IPTables) executeList(args []string) ([]string, error) {
 	var stdout bytes.Buffer
 	if err := ipt.runWithOutput(args, &stdout); err != nil {

+ 28 - 0
iptables/iptables_test.go

@@ -233,6 +233,11 @@ func runRulesTests(t *testing.T, ipt *IPTables) {
 		t.Fatalf("Delete failed: %v", err)
 	}
 
+	err = ipt.Append("filter", chain, "-s", address1, "-d", subnet2, "-j", "ACCEPT")
+	if err != nil {
+		t.Fatalf("Append failed: %v", err)
+	}
+
 	rules, err := ipt.List("filter", chain)
 	if err != nil {
 		t.Fatalf("List failed: %v", err)
@@ -243,6 +248,7 @@ func runRulesTests(t *testing.T, ipt *IPTables) {
 		"-A " + chain + " -s " + subnet1 + " -d " + address1 + " -j ACCEPT",
 		"-A " + chain + " -s " + subnet2 + " -d " + address2 + " -j ACCEPT",
 		"-A " + chain + " -s " + subnet2 + " -d " + address1 + " -j ACCEPT",
+		"-A " + chain + " -s " + address1 + " -d " + subnet2 + " -j ACCEPT",
 	}
 
 	if !reflect.DeepEqual(rules, expected) {
@@ -259,12 +265,34 @@ func runRulesTests(t *testing.T, ipt *IPTables) {
 		"-A " + chain + " -s " + subnet1 + " -d " + address1 + " -c 0 0 -j ACCEPT",
 		"-A " + chain + " -s " + subnet2 + " -d " + address2 + " -c 0 0 -j ACCEPT",
 		"-A " + chain + " -s " + subnet2 + " -d " + address1 + " -c 0 0 -j ACCEPT",
+		"-A " + chain + " -s " + address1 + " -d " + subnet2 + " -c 0 0 -j ACCEPT",
 	}
 
 	if !reflect.DeepEqual(rules, expected) {
 		t.Fatalf("ListWithCounters mismatch: \ngot  %#v \nneed %#v", rules, expected)
 	}
 
+	stats, err := ipt.Stats("filter", chain)
+	if err != nil {
+		t.Fatalf("Stats failed: %v", err)
+	}
+
+	opt := "--"
+	if ipt.proto == ProtocolIPv6 {
+		opt = "  "
+	}
+
+	expectedStats := [][]string{
+		{"0", "0", "ACCEPT", "all", opt, "*", "*", subnet1, address1, ""},
+		{"0", "0", "ACCEPT", "all", opt, "*", "*", subnet2, address2, ""},
+		{"0", "0", "ACCEPT", "all", opt, "*", "*", subnet2, address1, ""},
+		{"0", "0", "ACCEPT", "all", opt, "*", "*", address1, subnet2, ""},
+	}
+
+	if !reflect.DeepEqual(stats, expectedStats) {
+		t.Fatalf("Stats mismatch: \ngot  %#v \nneed %#v", stats, expectedStats)
+	}
+
 	// Clear the chain that was created.
 	err = ipt.ClearChain("filter", chain)
 	if err != nil {