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