github.com/fcwu/docker@v1.4.2-0.20150115145920-2a69ca89f0df/pkg/iptables/iptables.go (about) 1 package iptables 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "os/exec" 8 "regexp" 9 "strconv" 10 "strings" 11 12 log "github.com/Sirupsen/logrus" 13 ) 14 15 type Action string 16 type Table string 17 18 const ( 19 Append Action = "-A" 20 Delete Action = "-D" 21 Insert Action = "-I" 22 Nat Table = "nat" 23 Filter Table = "filter" 24 ) 25 26 var ( 27 supportsXlock = false 28 ErrIptablesNotFound = errors.New("Iptables not found") 29 ) 30 31 type Chain struct { 32 Name string 33 Bridge string 34 Table Table 35 } 36 37 type ChainError struct { 38 Chain string 39 Output []byte 40 } 41 42 func (e *ChainError) Error() string { 43 return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output)) 44 } 45 46 func init() { 47 supportsXlock = exec.Command("iptables", "--wait", "-L", "-n").Run() == nil 48 } 49 50 func NewChain(name, bridge string, table Table) (*Chain, error) { 51 c := &Chain{ 52 Name: name, 53 Bridge: bridge, 54 Table: table, 55 } 56 57 if string(c.Table) == "" { 58 c.Table = Filter 59 } 60 61 // Add chain if it doesn't exist 62 if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil { 63 if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil { 64 return nil, err 65 } else if len(output) != 0 { 66 return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output) 67 } 68 } 69 70 switch table { 71 case Nat: 72 preroute := []string{ 73 "-m", "addrtype", 74 "--dst-type", "LOCAL"} 75 if !Exists(preroute...) { 76 if err := c.Prerouting(Append, preroute...); err != nil { 77 return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) 78 } 79 } 80 output := []string{ 81 "-m", "addrtype", 82 "--dst-type", "LOCAL", 83 "!", "--dst", "127.0.0.0/8"} 84 if !Exists(output...) { 85 if err := c.Output(Append, output...); err != nil { 86 return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) 87 } 88 } 89 case Filter: 90 link := []string{"FORWARD", 91 "-o", c.Bridge, 92 "-j", c.Name} 93 if !Exists(link...) { 94 insert := append([]string{string(Insert)}, link...) 95 if output, err := Raw(insert...); err != nil { 96 return nil, err 97 } else if len(output) != 0 { 98 return nil, fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output) 99 } 100 } 101 } 102 return c, nil 103 } 104 105 func RemoveExistingChain(name string, table Table) error { 106 c := &Chain{ 107 Name: name, 108 Table: table, 109 } 110 if string(c.Table) == "" { 111 c.Table = Filter 112 } 113 return c.Remove() 114 } 115 116 // Add forwarding rule to 'filter' table and corresponding nat rule to 'nat' table 117 func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int) error { 118 daddr := ip.String() 119 if ip.IsUnspecified() { 120 // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we 121 // want "0.0.0.0/0". "0/0" is correctly interpreted as "any 122 // value" by both iptables and ip6tables. 123 daddr = "0/0" 124 } 125 if output, err := Raw("-t", string(Nat), string(action), c.Name, 126 "-p", proto, 127 "-d", daddr, 128 "--dport", strconv.Itoa(port), 129 "!", "-i", c.Bridge, 130 "-j", "DNAT", 131 "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil { 132 return err 133 } else if len(output) != 0 { 134 return &ChainError{Chain: "FORWARD", Output: output} 135 } 136 137 if output, err := Raw("-t", string(Filter), string(action), c.Name, 138 "!", "-i", c.Bridge, 139 "-o", c.Bridge, 140 "-p", proto, 141 "-d", destAddr, 142 "--dport", strconv.Itoa(destPort), 143 "-j", "ACCEPT"); err != nil { 144 return err 145 } else if len(output) != 0 { 146 return &ChainError{Chain: "FORWARD", Output: output} 147 } 148 149 if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING", 150 "-p", proto, 151 "-s", destAddr, 152 "-d", destAddr, 153 "--dport", strconv.Itoa(destPort), 154 "-j", "MASQUERADE"); err != nil { 155 return err 156 } else if len(output) != 0 { 157 return &ChainError{Chain: "FORWARD", Output: output} 158 } 159 160 return nil 161 } 162 163 // Add reciprocal ACCEPT rule for two supplied IP addresses. 164 // Traffic is allowed from ip1 to ip2 and vice-versa 165 func (c *Chain) Link(action Action, ip1, ip2 net.IP, port int, proto string) error { 166 if output, err := Raw("-t", string(Filter), string(action), c.Name, 167 "-i", c.Bridge, "-o", c.Bridge, 168 "-p", proto, 169 "-s", ip1.String(), 170 "-d", ip2.String(), 171 "--dport", strconv.Itoa(port), 172 "-j", "ACCEPT"); err != nil { 173 return err 174 } else if len(output) != 0 { 175 return fmt.Errorf("Error iptables forward: %s", output) 176 } 177 if output, err := Raw("-t", string(Filter), string(action), c.Name, 178 "-i", c.Bridge, "-o", c.Bridge, 179 "-p", proto, 180 "-s", ip2.String(), 181 "-d", ip1.String(), 182 "--sport", strconv.Itoa(port), 183 "-j", "ACCEPT"); err != nil { 184 return err 185 } else if len(output) != 0 { 186 return fmt.Errorf("Error iptables forward: %s", output) 187 } 188 return nil 189 } 190 191 // Add linking rule to nat/PREROUTING chain. 192 func (c *Chain) Prerouting(action Action, args ...string) error { 193 a := []string{"-t", string(Nat), string(action), "PREROUTING"} 194 if len(args) > 0 { 195 a = append(a, args...) 196 } 197 if output, err := Raw(append(a, "-j", c.Name)...); err != nil { 198 return err 199 } else if len(output) != 0 { 200 return &ChainError{Chain: "PREROUTING", Output: output} 201 } 202 return nil 203 } 204 205 // Add linking rule to an OUTPUT chain 206 func (c *Chain) Output(action Action, args ...string) error { 207 a := []string{"-t", string(c.Table), string(action), "OUTPUT"} 208 if len(args) > 0 { 209 a = append(a, args...) 210 } 211 if output, err := Raw(append(a, "-j", c.Name)...); err != nil { 212 return err 213 } else if len(output) != 0 { 214 return &ChainError{Chain: "OUTPUT", Output: output} 215 } 216 return nil 217 } 218 219 func (c *Chain) Remove() error { 220 // Ignore errors - This could mean the chains were never set up 221 if c.Table == Nat { 222 c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL") 223 c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8") 224 c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6 225 226 c.Prerouting(Delete) 227 c.Output(Delete) 228 } 229 Raw("-t", string(c.Table), "-F", c.Name) 230 Raw("-t", string(c.Table), "-X", c.Name) 231 return nil 232 } 233 234 // Check if a rule exists 235 func Exists(args ...string) bool { 236 // iptables -C, --check option was added in v.1.4.11 237 // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt 238 239 // try -C 240 // if exit status is 0 then return true, the rule exists 241 if _, err := Raw(append([]string{"-C"}, args...)...); err == nil { 242 return true 243 } 244 245 // parse iptables-save for the rule 246 rule := strings.Replace(strings.Join(args, " "), "-t nat ", "", -1) 247 existingRules, _ := exec.Command("iptables-save").Output() 248 249 // regex to replace ips in rule 250 // because MASQUERADE rule will not be exactly what was passed 251 re := regexp.MustCompile(`[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}`) 252 253 return strings.Contains( 254 re.ReplaceAllString(string(existingRules), "?"), 255 re.ReplaceAllString(rule, "?"), 256 ) 257 } 258 259 // Call 'iptables' system command, passing supplied arguments 260 func Raw(args ...string) ([]byte, error) { 261 path, err := exec.LookPath("iptables") 262 if err != nil { 263 return nil, ErrIptablesNotFound 264 } 265 266 if supportsXlock { 267 args = append([]string{"--wait"}, args...) 268 } 269 270 log.Debugf("%s, %v", path, args) 271 272 output, err := exec.Command(path, args...).CombinedOutput() 273 if err != nil { 274 return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) 275 } 276 277 // ignore iptables' message about xtables lock 278 if strings.Contains(string(output), "waiting for it to exit") { 279 output = []byte("") 280 } 281 282 return output, err 283 }