github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/mergeCode/libnetwork/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 // Action signifies the iptable action. 17 type Action string 18 19 // Table refers to Nat, Filter or Mangle. 20 type Table string 21 22 const ( 23 // Append appends the rule at the end of the chain. 24 Append Action = "-A" 25 // Delete deletes the rule from the chain. 26 Delete Action = "-D" 27 // Insert inserts the rule at the top of the chain. 28 Insert Action = "-I" 29 // Nat table is used for nat translation rules. 30 Nat Table = "nat" 31 // Filter table is used for filter rules. 32 Filter Table = "filter" 33 // Mangle table is used for mangling the packet. 34 Mangle Table = "mangle" 35 ) 36 37 var ( 38 iptablesPath string 39 supportsXlock = false 40 supportsCOpt = false 41 // used to lock iptables commands if xtables lock is not supported 42 bestEffortLock sync.Mutex 43 // ErrIptablesNotFound is returned when the rule is not found. 44 ErrIptablesNotFound = errors.New("Iptables not found") 45 probeOnce sync.Once 46 firewalldOnce sync.Once 47 ) 48 49 // ChainInfo defines the iptables chain. 50 type ChainInfo struct { 51 Name string 52 Table Table 53 HairpinMode bool 54 } 55 56 // ChainError is returned to represent errors during ip table operation. 57 type ChainError struct { 58 Chain string 59 Output []byte 60 } 61 62 func (e ChainError) Error() string { 63 return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output)) 64 } 65 66 func probe() { 67 if out, err := exec.Command("modprobe", "-va", "nf_nat").CombinedOutput(); err != nil { 68 logrus.Warnf("Running modprobe nf_nat failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err) 69 } 70 if out, err := exec.Command("modprobe", "-va", "xt_conntrack").CombinedOutput(); err != nil { 71 logrus.Warnf("Running modprobe xt_conntrack failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err) 72 } 73 } 74 75 func initFirewalld() { 76 if err := FirewalldInit(); err != nil { 77 logrus.Debugf("Fail to initialize firewalld: %v, using raw iptables instead", err) 78 } 79 } 80 81 func initCheck() error { 82 if iptablesPath == "" { 83 probeOnce.Do(probe) 84 firewalldOnce.Do(initFirewalld) 85 path, err := exec.LookPath("iptables") 86 if err != nil { 87 return ErrIptablesNotFound 88 } 89 iptablesPath = path 90 supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil 91 mj, mn, mc, err := GetVersion() 92 if err != nil { 93 logrus.Warnf("Failed to read iptables version: %v", err) 94 return nil 95 } 96 supportsCOpt = supportsCOption(mj, mn, mc) 97 } 98 return nil 99 } 100 101 // NewChain adds a new chain to ip table. 102 func NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) { 103 c := &ChainInfo{ 104 Name: name, 105 Table: table, 106 HairpinMode: hairpinMode, 107 } 108 if string(c.Table) == "" { 109 c.Table = Filter 110 } 111 112 // Add chain if it doesn't exist 113 if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil { 114 if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil { 115 return nil, err 116 } else if len(output) != 0 { 117 return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output) 118 } 119 } 120 return c, nil 121 } 122 123 // ProgramChain is used to add rules to a chain 124 func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error { 125 if c.Name == "" { 126 return fmt.Errorf("Could not program chain, missing chain name.") 127 } 128 129 switch c.Table { 130 case Nat: 131 preroute := []string{ 132 "-m", "addrtype", 133 "--dst-type", "LOCAL", 134 "-j", c.Name} 135 if !Exists(Nat, "PREROUTING", preroute...) && enable { 136 if err := c.Prerouting(Append, preroute...); err != nil { 137 return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) 138 } 139 } else if Exists(Nat, "PREROUTING", preroute...) && !enable { 140 if err := c.Prerouting(Delete, preroute...); err != nil { 141 return fmt.Errorf("Failed to remove docker in PREROUTING chain: %s", err) 142 } 143 } 144 output := []string{ 145 "-m", "addrtype", 146 "--dst-type", "LOCAL", 147 "-j", c.Name} 148 if !hairpinMode { 149 output = append(output, "!", "--dst", "127.0.0.0/8") 150 } 151 if !Exists(Nat, "OUTPUT", output...) && enable { 152 if err := c.Output(Append, output...); err != nil { 153 return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) 154 } 155 } else if Exists(Nat, "OUTPUT", output...) && !enable { 156 if err := c.Output(Delete, output...); err != nil { 157 return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) 158 } 159 } 160 case Filter: 161 if bridgeName == "" { 162 return fmt.Errorf("Could not program chain %s/%s, missing bridge name.", 163 c.Table, c.Name) 164 } 165 link := []string{ 166 "-o", bridgeName, 167 "-j", c.Name} 168 if !Exists(Filter, "FORWARD", link...) && enable { 169 insert := append([]string{string(Insert), "FORWARD"}, link...) 170 if output, err := Raw(insert...); err != nil { 171 return err 172 } else if len(output) != 0 { 173 return fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output) 174 } 175 } else if Exists(Filter, "FORWARD", link...) && !enable { 176 del := append([]string{string(Delete), "FORWARD"}, link...) 177 if output, err := Raw(del...); err != nil { 178 return err 179 } else if len(output) != 0 { 180 return fmt.Errorf("Could not delete linking rule from %s/%s: %s", c.Table, c.Name, output) 181 } 182 183 } 184 } 185 return nil 186 } 187 188 // RemoveExistingChain removes existing chain from the table. 189 func RemoveExistingChain(name string, table Table) error { 190 c := &ChainInfo{ 191 Name: name, 192 Table: table, 193 } 194 if string(c.Table) == "" { 195 c.Table = Filter 196 } 197 return c.Remove() 198 } 199 200 // Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table. 201 func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int, bridgeName string) error { 202 daddr := ip.String() 203 if ip.IsUnspecified() { 204 // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we 205 // want "0.0.0.0/0". "0/0" is correctly interpreted as "any 206 // value" by both iptables and ip6tables. 207 daddr = "0/0" 208 } 209 210 args := []string{ 211 "-p", proto, 212 "-d", daddr, 213 "--dport", strconv.Itoa(port), 214 "-j", "DNAT", 215 "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))} 216 if !c.HairpinMode { 217 args = append(args, "!", "-i", bridgeName) 218 } 219 if err := ProgramRule(Nat, c.Name, action, args); err != nil { 220 return err 221 } 222 223 args = []string{ 224 "!", "-i", bridgeName, 225 "-o", bridgeName, 226 "-p", proto, 227 "-d", destAddr, 228 "--dport", strconv.Itoa(destPort), 229 "-j", "ACCEPT", 230 } 231 if err := ProgramRule(Filter, c.Name, action, args); err != nil { 232 return err 233 } 234 235 args = []string{ 236 "-p", proto, 237 "-s", destAddr, 238 "-d", destAddr, 239 "--dport", strconv.Itoa(destPort), 240 "-j", "MASQUERADE", 241 } 242 if err := ProgramRule(Nat, "POSTROUTING", action, args); err != nil { 243 return err 244 } 245 246 return nil 247 } 248 249 // Link adds reciprocal ACCEPT rule for two supplied IP addresses. 250 // Traffic is allowed from ip1 to ip2 and vice-versa 251 func (c *ChainInfo) Link(action Action, ip1, ip2 net.IP, port int, proto string, bridgeName string) error { 252 // forward 253 args := []string{ 254 "-i", bridgeName, "-o", bridgeName, 255 "-p", proto, 256 "-s", ip1.String(), 257 "-d", ip2.String(), 258 "--dport", strconv.Itoa(port), 259 "-j", "ACCEPT", 260 } 261 if err := ProgramRule(Filter, c.Name, action, args); err != nil { 262 return err 263 } 264 // reverse 265 args[7], args[9] = args[9], args[7] 266 args[10] = "--sport" 267 if err := ProgramRule(Filter, c.Name, action, args); err != nil { 268 return err 269 } 270 return nil 271 } 272 273 // ProgramRule adds the rule specified by args only if the 274 // rule is not already present in the chain. Reciprocally, 275 // it removes the rule only if present. 276 func ProgramRule(table Table, chain string, action Action, args []string) error { 277 if Exists(table, chain, args...) != (action == Delete) { 278 return nil 279 } 280 return RawCombinedOutput(append([]string{"-t", string(table), string(action), chain}, args...)...) 281 } 282 283 // Prerouting adds linking rule to nat/PREROUTING chain. 284 func (c *ChainInfo) Prerouting(action Action, args ...string) error { 285 a := []string{"-t", string(Nat), string(action), "PREROUTING"} 286 if len(args) > 0 { 287 a = append(a, args...) 288 } 289 if output, err := Raw(a...); err != nil { 290 return err 291 } else if len(output) != 0 { 292 return ChainError{Chain: "PREROUTING", Output: output} 293 } 294 return nil 295 } 296 297 // Output adds linking rule to an OUTPUT chain. 298 func (c *ChainInfo) Output(action Action, args ...string) error { 299 a := []string{"-t", string(c.Table), string(action), "OUTPUT"} 300 if len(args) > 0 { 301 a = append(a, args...) 302 } 303 if output, err := Raw(a...); err != nil { 304 return err 305 } else if len(output) != 0 { 306 return ChainError{Chain: "OUTPUT", Output: output} 307 } 308 return nil 309 } 310 311 // Remove removes the chain. 312 func (c *ChainInfo) Remove() error { 313 // Ignore errors - This could mean the chains were never set up 314 if c.Table == Nat { 315 c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) 316 c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", c.Name) 317 c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) // Created in versions <= 0.1.6 318 319 c.Prerouting(Delete) 320 c.Output(Delete) 321 } 322 Raw("-t", string(c.Table), "-F", c.Name) 323 Raw("-t", string(c.Table), "-X", c.Name) 324 return nil 325 } 326 327 // Exists checks if a rule exists 328 func Exists(table Table, chain string, rule ...string) bool { 329 if string(table) == "" { 330 table = Filter 331 } 332 333 initCheck() 334 335 if supportsCOpt { 336 // if exit status is 0 then return true, the rule exists 337 _, err := Raw(append([]string{"-t", string(table), "-C", chain}, rule...)...) 338 return err == nil 339 } 340 341 // parse "iptables -S" for the rule (it checks rules in a specific chain 342 // in a specific table and it is very unreliable) 343 return existsRaw(table, chain, rule...) 344 } 345 346 func existsRaw(table Table, chain string, rule ...string) bool { 347 ruleString := fmt.Sprintf("%s %s\n", chain, strings.Join(rule, " ")) 348 existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output() 349 350 return strings.Contains(string(existingRules), ruleString) 351 } 352 353 // Raw calls 'iptables' system command, passing supplied arguments. 354 func Raw(args ...string) ([]byte, error) { 355 if firewalldRunning { 356 output, err := Passthrough(Iptables, args...) 357 if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") { 358 return output, err 359 } 360 } 361 return raw(args...) 362 } 363 364 func raw(args ...string) ([]byte, error) { 365 if err := initCheck(); err != nil { 366 return nil, err 367 } 368 if supportsXlock { 369 args = append([]string{"--wait"}, args...) 370 } else { 371 bestEffortLock.Lock() 372 defer bestEffortLock.Unlock() 373 } 374 375 logrus.Debugf("%s, %v", iptablesPath, args) 376 377 output, err := exec.Command(iptablesPath, args...).CombinedOutput() 378 if err != nil { 379 return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) 380 } 381 382 // ignore iptables' message about xtables lock 383 if strings.Contains(string(output), "waiting for it to exit") { 384 output = []byte("") 385 } 386 387 return output, err 388 } 389 390 // RawCombinedOutput inernally calls the Raw function and returns a non nil 391 // error if Raw returned a non nil error or a non empty output 392 func RawCombinedOutput(args ...string) error { 393 if output, err := Raw(args...); err != nil || len(output) != 0 { 394 return fmt.Errorf("%s (%v)", string(output), err) 395 } 396 return nil 397 } 398 399 // RawCombinedOutputNative behave as RawCombinedOutput with the difference it 400 // will always invoke `iptables` binary 401 func RawCombinedOutputNative(args ...string) error { 402 if output, err := raw(args...); err != nil || len(output) != 0 { 403 return fmt.Errorf("%s (%v)", string(output), err) 404 } 405 return nil 406 } 407 408 // ExistChain checks if a chain exists 409 func ExistChain(chain string, table Table) bool { 410 if _, err := Raw("-t", string(table), "-L", chain); err == nil { 411 return true 412 } 413 return false 414 } 415 416 // GetVersion reads the iptables version numbers 417 func GetVersion() (major, minor, micro int, err error) { 418 out, err := Raw("--version") 419 if err == nil { 420 major, minor, micro = parseVersionNumbers(string(out)) 421 } 422 return 423 } 424 425 func parseVersionNumbers(input string) (major, minor, micro int) { 426 re := regexp.MustCompile(`v\d*.\d*.\d*`) 427 line := re.FindString(input) 428 fmt.Sscanf(line, "v%d.%d.%d", &major, &minor, µ) 429 return 430 } 431 432 // iptables -C, --check option was added in v.1.4.11 433 // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt 434 func supportsCOption(mj, mn, mc int) bool { 435 return mj > 1 || (mj == 1 && (mn > 4 || (mn == 4 && mc >= 11))) 436 }