coreos / go-iptables

Go wrapper around iptables utility
Apache License 2.0
1.11k stars 256 forks source link

Cannot parse negated CIDR ex: "!192.168.122.0/24" #70

Open stealthybox opened 4 years ago

stealthybox commented 4 years ago

Calling ipt.StructuredStats("nat", "POSTROUTING") when rules have negated ranges can error:

invalid CIDR address: !192.168.122.0/24%!(EXTRA string=could not parse destination)

On my machine, I set up a virtual bridge for this subnet, and these iptables rules were auto-created:

ifconfig virbr0; \
sudo iptables -L | grep 192.168; \
sudo iptables -t nat -L POSTROUTING | grep 192.168 \

virbr0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
        ether 06:79:79:15:1a:de  txqueuelen 1000  (Ethernet)
        RX packets 76642  bytes 4332785 (4.3 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 476675  bytes 726146534 (726.1 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ACCEPT     all  --  anywhere             192.168.122.0/24     ctstate RELATED,ESTABLISHED
ACCEPT     all  --  192.168.122.0/24     anywhere            
RETURN     all  --  192.168.122.0/24     base-address.mcast.net/24 
RETURN     all  --  192.168.122.0/24     255.255.255.255     
MASQUERADE  tcp  --  192.168.122.0/24    !192.168.122.0/24     masq ports: 1024-65535
MASQUERADE  udp  --  192.168.122.0/24    !192.168.122.0/24     masq ports: 1024-65535
MASQUERADE  all  --  192.168.122.0/24    !192.168.122.0/24   

ignite uses this library call to cleanup chains, and these MASQ rules fail to net.ParseCIDR due to the leading exclamation mark negating the subnet: https://github.com/weaveworks/ignite/issues/393


Here's a minimal reproduction:

Test Code ```go package main import ( "fmt" "github.com/coreos/go-iptables/iptables" ) // testActual is a forked version of ipt.StructuredStats() that prints debug info func testActual(ipt *iptables.IPTables) ([]iptables.Stat, error) { table := "nat" chain := "POSTROUTING" rawStats, err := ipt.Stats(table, chain) if err != nil { return nil, err } structStats := []iptables.Stat{} for _, rawStat := range rawStats { fmt.Println(rawStat[7], rawStat[8]) stat, err := ipt.ParseStat(rawStat) if err != nil { fmt.Println("rawStat: ", rawStat) fmt.Println("stat: ", stat) fmt.Println("err: ", err) return nil, err } structStats = append(structStats, stat) } return structStats, nil } func minimalReproduction(ipt *iptables.IPTables) (iptables.Stat, error) { return ipt.ParseStat( []string{ "1859", "112600", "MASQUERADE", "tcp", "--", "*", "*", "192.168.122.0/24", "!192.168.122.0/24", "masq ports: 1024-65535", }, ) } func main() { ipt, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4) fmt.Println("minimalReproduction") fmt.Println(minimalReproduction(ipt)) fmt.Println() fmt.Println("testActual") fmt.Println(testActual(ipt)) } ```

Test logs:

sudo $(which go) run ./testnet.go
minimalReproduction
{1859 112600      192.168.122.0/24 <nil> } invalid CIDR address: !192.168.122.0/24%!(EXTRA string=could not parse destination)

testActual
172.19.0.0/16 0.0.0.0/0
172.17.0.0/16 0.0.0.0/0
192.168.122.0/24 224.0.0.0/24
192.168.122.0/24 255.255.255.255/32
192.168.122.0/24 !192.168.122.0/24
rawStat:  [1883 114040 MASQUERADE tcp -- * * 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535]
stat:     {1883 114040      192.168.122.0/24 <nil> }
err:      invalid CIDR address: !192.168.122.0/24%!(EXTRA string=could not parse destination)
[] invalid CIDR address: !192.168.122.0/24%!(EXTRA string=could not parse destination)
nor1su commented 3 months ago
type CustomStat struct {
    Packets     uint64 `json:"pkts"`
    Bytes       uint64 `json:"bytes"`
    Target      string `json:"target"`
    Protocol    string `json:"prot"`
    Opt         string `json:"opt"`
    Input       string `json:"in"`
    Output      string `json:"out"`
    Source      string `json:"source"`
    Destination string `json:"destination"`
    Port        string `json:"port"`
    Options     string `json:"options"`
}

func parseIptablesOutput(output string) ([]CustomStat, error) {
    var customStats []CustomStat
    scanner := bufio.NewScanner(strings.NewReader(output))
    for scanner.Scan() {
        line := scanner.Text()
        fields := strings.Fields(line)
        if len(fields) < 10 {
            continue
        }
        packets, _ := strconv.ParseUint(fields[0], 10, 64)
        bytes, _ := strconv.ParseUint(fields[1], 10, 64)
        customStats = append(customStats, CustomStat{
            Packets:     packets,
            Bytes:       bytes,
            Target:      fields[2],
            Protocol:    fields[3],
            Opt:         fields[4],
            Input:       fields[5],
            Output:      fields[6],
            Source:      fields[7],
            Destination: fields[8],
            Options:     strings.Join(fields[9:], " "),
        })
    }
    return customStats, scanner.Err()
}