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