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