2015-05-06 19:47:41 -04:00
package iptables
import (
"errors"
"fmt"
"net"
"os/exec"
2016-02-02 15:52:34 -05:00
"regexp"
2015-05-06 19:47:41 -04:00
"strconv"
"strings"
2015-05-12 23:27:22 -04:00
"sync"
2018-04-11 11:46:18 -04:00
"time"
2015-05-06 19:47:41 -04:00
2017-07-26 17:18:31 -04:00
"github.com/sirupsen/logrus"
2015-05-06 19:47:41 -04:00
)
2015-05-18 19:49:12 -04:00
// Action signifies the iptable action.
2015-05-06 19:47:41 -04:00
type Action string
2016-10-28 16:54:52 -04:00
// Policy is the default iptable policies
type Policy string
2015-05-18 19:49:12 -04:00
// Table refers to Nat, Filter or Mangle.
2015-05-06 19:47:41 -04:00
type Table string
2017-11-28 16:15:55 -05:00
// IPVersion refers to IP version, v4 or v6
type IPVersion string
2015-05-06 19:47:41 -04:00
const (
2015-05-18 19:49:12 -04:00
// Append appends the rule at the end of the chain.
2015-05-06 19:47:41 -04:00
Append Action = "-A"
2015-05-18 19:49:12 -04:00
// Delete deletes the rule from the chain.
2015-05-06 19:47:41 -04:00
Delete Action = "-D"
2015-05-18 19:49:12 -04:00
// Insert inserts the rule at the top of the chain.
2015-05-06 19:47:41 -04:00
Insert Action = "-I"
2015-05-18 19:49:12 -04:00
// Nat table is used for nat translation rules.
2015-05-06 19:47:41 -04:00
Nat Table = "nat"
2015-05-18 19:49:12 -04:00
// Filter table is used for filter rules.
2015-05-06 19:47:41 -04:00
Filter Table = "filter"
2015-05-18 19:49:12 -04:00
// Mangle table is used for mangling the packet.
2015-05-06 19:47:41 -04:00
Mangle Table = "mangle"
2016-10-28 16:54:52 -04:00
// Drop is the default iptables DROP policy
Drop Policy = "DROP"
// Accept is the default iptables ACCEPT policy
Accept Policy = "ACCEPT"
2017-11-28 16:15:55 -05:00
// IPv4 is version 4
IPv4 IPVersion = "IPV4"
// IPv6 is version 6
IPv6 IPVersion = "IPV6"
2015-05-06 19:47:41 -04:00
)
var (
iptablesPath string
2017-11-28 16:15:55 -05:00
ip6tablesPath string
2015-05-06 19:47:41 -04:00
supportsXlock = false
2016-02-02 15:52:34 -05:00
supportsCOpt = false
2018-04-11 11:46:18 -04:00
xLockWaitMsg = "Another app is currently holding the xtables lock"
2015-05-12 23:27:22 -04:00
// used to lock iptables commands if xtables lock is not supported
bestEffortLock sync . Mutex
2015-05-18 19:49:12 -04:00
// ErrIptablesNotFound is returned when the rule is not found.
2015-05-06 19:47:41 -04:00
ErrIptablesNotFound = errors . New ( "Iptables not found" )
2017-03-08 13:37:13 -05:00
initOnce sync . Once
2015-05-06 19:47:41 -04:00
)
2020-07-22 09:08:22 -04:00
// IPTable defines struct with IPVersion
2017-11-28 16:15:55 -05:00
type IPTable struct {
Version IPVersion
}
2015-06-11 21:12:00 -04:00
// ChainInfo defines the iptables chain.
type ChainInfo struct {
2015-06-11 19:53:20 -04:00
Name string
Table Table
HairpinMode bool
2017-11-28 16:15:55 -05:00
IPTable IPTable
2015-05-06 19:47:41 -04:00
}
2015-05-18 19:49:12 -04:00
// ChainError is returned to represent errors during ip table operation.
2015-05-06 19:47:41 -04:00
type ChainError struct {
Chain string
Output [ ] byte
}
func ( e ChainError ) Error ( ) string {
return fmt . Sprintf ( "Error iptables %s: %s" , e . Chain , string ( e . Output ) )
}
2016-03-21 16:40:43 -04:00
func probe ( ) {
2019-03-26 15:55:49 -04:00
path , err := exec . LookPath ( "iptables" )
if err != nil {
logrus . Warnf ( "Failed to find iptables: %v" , err )
return
2016-03-21 16:40:43 -04:00
}
2019-03-26 15:55:49 -04:00
if out , err := exec . Command ( path , "--wait" , "-t" , "nat" , "-L" , "-n" ) . CombinedOutput ( ) ; err != nil {
logrus . Warnf ( "Running iptables --wait -t nat -L -n failed with message: `%s`, error: %v" , strings . TrimSpace ( string ( out ) ) , err )
2016-03-21 16:40:43 -04:00
}
2017-11-28 16:15:55 -05:00
_ , err = exec . LookPath ( "ip6tables" )
if err != nil {
logrus . Warnf ( "Failed to find ip6tables: %v" , err )
return
}
2016-03-21 16:40:43 -04:00
}
func initFirewalld ( ) {
if err := FirewalldInit ( ) ; err != nil {
logrus . Debugf ( "Fail to initialize firewalld: %v, using raw iptables instead" , err )
}
}
2017-03-08 13:37:13 -05:00
func detectIptables ( ) {
2019-03-01 14:43:02 -05:00
path , err := exec . LookPath ( "iptables" )
2017-03-08 13:37:13 -05:00
if err != nil {
2019-03-01 14:43:02 -05:00
return
2017-03-08 13:37:13 -05:00
}
iptablesPath = path
2017-11-28 16:15:55 -05:00
path , err = exec . LookPath ( "ip6tables" )
if err != nil {
return
}
ip6tablesPath = path
2017-03-08 13:37:13 -05:00
supportsXlock = exec . Command ( iptablesPath , "--wait" , "-L" , "-n" ) . Run ( ) == nil
mj , mn , mc , err := GetVersion ( )
if err != nil {
logrus . Warnf ( "Failed to read iptables version: %v" , err )
return
}
supportsCOpt = supportsCOption ( mj , mn , mc )
}
2017-04-06 15:26:08 -04:00
func initDependencies ( ) {
2017-03-08 13:37:13 -05:00
probe ( )
initFirewalld ( )
detectIptables ( )
}
2015-05-06 19:47:41 -04:00
func initCheck ( ) error {
2017-04-06 15:26:08 -04:00
initOnce . Do ( initDependencies )
2017-03-08 13:37:13 -05:00
2015-05-06 19:47:41 -04:00
if iptablesPath == "" {
2017-03-08 13:37:13 -05:00
return ErrIptablesNotFound
2015-05-06 19:47:41 -04:00
}
return nil
}
2017-11-28 16:15:55 -05:00
// GetIptable returns an instance of IPTable with specified version
func GetIptable ( version IPVersion ) * IPTable {
return & IPTable { Version : version }
}
2015-05-18 19:49:12 -04:00
// NewChain adds a new chain to ip table.
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) NewChain ( name string , table Table , hairpinMode bool ) ( * ChainInfo , error ) {
2015-06-11 21:12:00 -04:00
c := & ChainInfo {
2015-06-11 19:53:20 -04:00
Name : name ,
Table : table ,
HairpinMode : hairpinMode ,
2017-11-28 16:15:55 -05:00
IPTable : iptable ,
2015-05-06 19:47:41 -04:00
}
if string ( c . Table ) == "" {
c . Table = Filter
}
// Add chain if it doesn't exist
2017-11-28 16:15:55 -05:00
if _ , err := iptable . Raw ( "-t" , string ( c . Table ) , "-n" , "-L" , c . Name ) ; err != nil {
if output , err := iptable . Raw ( "-t" , string ( c . Table ) , "-N" , c . Name ) ; err != nil {
2015-05-06 19:47:41 -04:00
return nil , err
} else if len ( output ) != 0 {
return nil , fmt . Errorf ( "Could not create %s/%s chain: %s" , c . Table , c . Name , output )
}
}
2015-06-11 21:12:00 -04:00
return c , nil
}
2017-11-28 16:15:55 -05:00
// LoopbackByVersion returns loopback address by version
func ( iptable IPTable ) LoopbackByVersion ( ) string {
if iptable . Version == IPv6 {
return "::1/128"
}
return "127.0.0.0/8"
}
2015-06-11 21:12:00 -04:00
// ProgramChain is used to add rules to a chain
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) ProgramChain ( c * ChainInfo , bridgeName string , hairpinMode , enable bool ) error {
2015-06-11 21:12:00 -04:00
if c . Name == "" {
2016-11-21 20:29:53 -05:00
return errors . New ( "Could not program chain, missing chain name" )
2015-06-11 21:12:00 -04:00
}
2015-05-06 19:47:41 -04:00
2020-05-04 16:51:42 -04:00
// Either add or remove the interface from the firewalld zone
if firewalldRunning {
if enable {
if err := AddInterfaceFirewalld ( bridgeName ) ; err != nil {
return err
}
} else {
if err := DelInterfaceFirewalld ( bridgeName ) ; err != nil {
return err
}
}
}
2015-06-11 21:12:00 -04:00
switch c . Table {
2015-05-06 19:47:41 -04:00
case Nat :
preroute := [ ] string {
"-m" , "addrtype" ,
2015-06-16 02:55:29 -04:00
"--dst-type" , "LOCAL" ,
"-j" , c . Name }
2017-11-28 16:15:55 -05:00
if ! iptable . Exists ( Nat , "PREROUTING" , preroute ... ) && enable {
2015-05-06 19:47:41 -04:00
if err := c . Prerouting ( Append , preroute ... ) ; err != nil {
2017-07-21 13:44:41 -04:00
return fmt . Errorf ( "Failed to inject %s in PREROUTING chain: %s" , c . Name , err )
2015-05-06 19:47:41 -04:00
}
2017-11-28 16:15:55 -05:00
} else if iptable . Exists ( Nat , "PREROUTING" , preroute ... ) && ! enable {
2015-10-05 01:53:45 -04:00
if err := c . Prerouting ( Delete , preroute ... ) ; err != nil {
2017-07-21 13:44:41 -04:00
return fmt . Errorf ( "Failed to remove %s in PREROUTING chain: %s" , c . Name , err )
2015-10-05 01:53:45 -04:00
}
2015-05-06 19:47:41 -04:00
}
output := [ ] string {
"-m" , "addrtype" ,
2015-06-16 02:55:29 -04:00
"--dst-type" , "LOCAL" ,
"-j" , c . Name }
2015-05-18 19:49:12 -04:00
if ! hairpinMode {
2017-11-28 16:15:55 -05:00
output = append ( output , "!" , "--dst" , iptable . LoopbackByVersion ( ) )
2015-05-18 19:49:12 -04:00
}
2017-11-28 16:15:55 -05:00
if ! iptable . Exists ( Nat , "OUTPUT" , output ... ) && enable {
2015-05-06 19:47:41 -04:00
if err := c . Output ( Append , output ... ) ; err != nil {
2017-07-21 13:44:41 -04:00
return fmt . Errorf ( "Failed to inject %s in OUTPUT chain: %s" , c . Name , err )
2015-05-06 19:47:41 -04:00
}
2017-11-28 16:15:55 -05:00
} else if iptable . Exists ( Nat , "OUTPUT" , output ... ) && ! enable {
2015-10-05 01:53:45 -04:00
if err := c . Output ( Delete , output ... ) ; err != nil {
2017-07-21 13:44:41 -04:00
return fmt . Errorf ( "Failed to inject %s in OUTPUT chain: %s" , c . Name , err )
2015-10-05 01:53:45 -04:00
}
2015-05-06 19:47:41 -04:00
}
case Filter :
2015-06-11 21:12:00 -04:00
if bridgeName == "" {
2016-11-14 19:41:54 -05:00
return fmt . Errorf ( "Could not program chain %s/%s, missing bridge name" ,
2015-06-11 21:12:00 -04:00
c . Table , c . Name )
}
2015-05-06 19:47:41 -04:00
link := [ ] string {
2015-06-11 21:12:00 -04:00
"-o" , bridgeName ,
2015-05-06 19:47:41 -04:00
"-j" , c . Name }
2017-11-28 16:15:55 -05:00
if ! iptable . Exists ( Filter , "FORWARD" , link ... ) && enable {
2015-05-06 19:47:41 -04:00
insert := append ( [ ] string { string ( Insert ) , "FORWARD" } , link ... )
2017-11-28 16:15:55 -05:00
if output , err := iptable . Raw ( insert ... ) ; err != nil {
2015-06-11 21:12:00 -04:00
return err
2015-05-06 19:47:41 -04:00
} else if len ( output ) != 0 {
2015-06-11 21:12:00 -04:00
return fmt . Errorf ( "Could not create linking rule to %s/%s: %s" , c . Table , c . Name , output )
2015-05-06 19:47:41 -04:00
}
2017-11-28 16:15:55 -05:00
} else if iptable . Exists ( Filter , "FORWARD" , link ... ) && ! enable {
2015-10-05 01:53:45 -04:00
del := append ( [ ] string { string ( Delete ) , "FORWARD" } , link ... )
2017-11-28 16:15:55 -05:00
if output , err := iptable . Raw ( del ... ) ; err != nil {
2015-10-05 01:53:45 -04:00
return err
} else if len ( output ) != 0 {
return fmt . Errorf ( "Could not delete linking rule from %s/%s: %s" , c . Table , c . Name , output )
}
2015-05-06 19:47:41 -04:00
}
2016-02-24 07:43:32 -05:00
establish := [ ] string {
"-o" , bridgeName ,
"-m" , "conntrack" ,
"--ctstate" , "RELATED,ESTABLISHED" ,
"-j" , "ACCEPT" }
2017-11-28 16:15:55 -05:00
if ! iptable . Exists ( Filter , "FORWARD" , establish ... ) && enable {
2016-02-24 07:43:32 -05:00
insert := append ( [ ] string { string ( Insert ) , "FORWARD" } , establish ... )
2017-11-28 16:15:55 -05:00
if output , err := iptable . Raw ( insert ... ) ; err != nil {
2016-02-24 07:43:32 -05:00
return err
} else if len ( output ) != 0 {
return fmt . Errorf ( "Could not create establish rule to %s: %s" , c . Table , output )
}
2017-11-28 16:15:55 -05:00
} else if iptable . Exists ( Filter , "FORWARD" , establish ... ) && ! enable {
2016-02-24 07:43:32 -05:00
del := append ( [ ] string { string ( Delete ) , "FORWARD" } , establish ... )
2017-11-28 16:15:55 -05:00
if output , err := iptable . Raw ( del ... ) ; err != nil {
2016-02-24 07:43:32 -05:00
return err
} else if len ( output ) != 0 {
return fmt . Errorf ( "Could not delete establish rule from %s: %s" , c . Table , output )
}
}
2015-05-06 19:47:41 -04:00
}
2015-06-11 21:12:00 -04:00
return nil
2015-05-06 19:47:41 -04:00
}
2015-05-18 19:49:12 -04:00
// RemoveExistingChain removes existing chain from the table.
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) RemoveExistingChain ( name string , table Table ) error {
2015-06-11 21:12:00 -04:00
c := & ChainInfo {
2017-11-28 16:15:55 -05:00
Name : name ,
Table : table ,
IPTable : iptable ,
2015-05-06 19:47:41 -04:00
}
if string ( c . Table ) == "" {
c . Table = Filter
}
return c . Remove ( )
}
2015-05-18 19:49:12 -04:00
// Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table.
2015-06-11 21:12:00 -04:00
func ( c * ChainInfo ) Forward ( action Action , ip net . IP , port int , proto , destAddr string , destPort int , bridgeName string ) error {
2017-11-28 16:15:55 -05:00
iptable := GetIptable ( c . IPTable . Version )
2015-05-06 19:47:41 -04:00
daddr := ip . String ( )
if ip . IsUnspecified ( ) {
// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
// want "0.0.0.0/0". "0/0" is correctly interpreted as "any
// value" by both iptables and ip6tables.
daddr = "0/0"
}
2016-09-30 14:08:38 -04:00
args := [ ] string {
2015-05-06 19:47:41 -04:00
"-p" , proto ,
"-d" , daddr ,
"--dport" , strconv . Itoa ( port ) ,
"-j" , "DNAT" ,
2015-06-11 19:53:20 -04:00
"--to-destination" , net . JoinHostPort ( destAddr , strconv . Itoa ( destPort ) ) }
2017-11-28 16:15:55 -05:00
2015-06-11 19:53:20 -04:00
if ! c . HairpinMode {
2015-06-11 21:12:00 -04:00
args = append ( args , "!" , "-i" , bridgeName )
2015-06-11 19:53:20 -04:00
}
2017-11-28 16:15:55 -05:00
if err := iptable . ProgramRule ( Nat , c . Name , action , args ) ; err != nil {
2015-05-06 19:47:41 -04:00
return err
}
2016-09-30 14:08:38 -04:00
args = [ ] string {
2015-06-11 21:12:00 -04:00
"!" , "-i" , bridgeName ,
"-o" , bridgeName ,
2015-05-06 19:47:41 -04:00
"-p" , proto ,
"-d" , destAddr ,
"--dport" , strconv . Itoa ( destPort ) ,
2016-09-30 14:08:38 -04:00
"-j" , "ACCEPT" ,
}
2017-11-28 16:15:55 -05:00
if err := iptable . ProgramRule ( Filter , c . Name , action , args ) ; err != nil {
2015-05-06 19:47:41 -04:00
return err
}
2016-09-30 14:08:38 -04:00
args = [ ] string {
2015-05-06 19:47:41 -04:00
"-p" , proto ,
"-s" , destAddr ,
"-d" , destAddr ,
"--dport" , strconv . Itoa ( destPort ) ,
2016-09-30 14:08:38 -04:00
"-j" , "MASQUERADE" ,
}
2017-06-13 01:29:56 -04:00
2017-11-28 16:15:55 -05:00
if err := iptable . ProgramRule ( Nat , "POSTROUTING" , action , args ) ; err != nil {
2017-06-13 01:29:56 -04:00
return err
}
if proto == "sctp" {
// Linux kernel v4.9 and below enables NETIF_F_SCTP_CRC for veth by
// the following commit.
// This introduces a problem when conbined with a physical NIC without
// NETIF_F_SCTP_CRC. As for a workaround, here we add an iptables entry
// to fill the checksum.
//
// https://github.com/torvalds/linux/commit/c80fafbbb59ef9924962f83aac85531039395b18
args = [ ] string {
"-p" , proto ,
"--sport" , strconv . Itoa ( destPort ) ,
"-j" , "CHECKSUM" ,
"--checksum-fill" ,
}
2017-11-28 16:15:55 -05:00
if err := iptable . ProgramRule ( Mangle , "POSTROUTING" , action , args ) ; err != nil {
2017-06-13 01:29:56 -04:00
return err
}
}
return nil
2015-05-06 19:47:41 -04:00
}
2015-05-18 19:49:12 -04:00
// Link adds reciprocal ACCEPT rule for two supplied IP addresses.
2015-05-06 19:47:41 -04:00
// Traffic is allowed from ip1 to ip2 and vice-versa
2015-06-11 21:12:00 -04:00
func ( c * ChainInfo ) Link ( action Action , ip1 , ip2 net . IP , port int , proto string , bridgeName string ) error {
2017-11-28 16:15:55 -05:00
iptable := GetIptable ( c . IPTable . Version )
2016-09-30 14:08:38 -04:00
// forward
args := [ ] string {
2015-06-11 21:12:00 -04:00
"-i" , bridgeName , "-o" , bridgeName ,
2015-05-06 19:47:41 -04:00
"-p" , proto ,
"-s" , ip1 . String ( ) ,
"-d" , ip2 . String ( ) ,
"--dport" , strconv . Itoa ( port ) ,
2016-09-30 14:08:38 -04:00
"-j" , "ACCEPT" ,
}
2017-11-28 16:15:55 -05:00
if err := iptable . ProgramRule ( Filter , c . Name , action , args ) ; err != nil {
2015-05-06 19:47:41 -04:00
return err
}
2016-09-30 14:08:38 -04:00
// reverse
args [ 7 ] , args [ 9 ] = args [ 9 ] , args [ 7 ]
args [ 10 ] = "--sport"
2017-11-28 16:15:55 -05:00
return iptable . ProgramRule ( Filter , c . Name , action , args )
2015-05-06 19:47:41 -04:00
}
2016-09-30 14:08:38 -04:00
// ProgramRule adds the rule specified by args only if the
// rule is not already present in the chain. Reciprocally,
// it removes the rule only if present.
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) ProgramRule ( table Table , chain string , action Action , args [ ] string ) error {
if iptable . Exists ( table , chain , args ... ) != ( action == Delete ) {
2016-09-30 14:08:38 -04:00
return nil
}
2017-11-28 16:15:55 -05:00
return iptable . RawCombinedOutput ( append ( [ ] string { "-t" , string ( table ) , string ( action ) , chain } , args ... ) ... )
2016-09-30 14:08:38 -04:00
}
2015-05-18 19:49:12 -04:00
// Prerouting adds linking rule to nat/PREROUTING chain.
2015-06-11 21:12:00 -04:00
func ( c * ChainInfo ) Prerouting ( action Action , args ... string ) error {
2017-11-28 16:15:55 -05:00
iptable := GetIptable ( c . IPTable . Version )
2015-05-06 19:47:41 -04:00
a := [ ] string { "-t" , string ( Nat ) , string ( action ) , "PREROUTING" }
if len ( args ) > 0 {
a = append ( a , args ... )
}
2017-11-28 16:15:55 -05:00
if output , err := iptable . Raw ( a ... ) ; err != nil {
2015-05-06 19:47:41 -04:00
return err
} else if len ( output ) != 0 {
return ChainError { Chain : "PREROUTING" , Output : output }
}
return nil
}
2015-05-18 19:49:12 -04:00
// Output adds linking rule to an OUTPUT chain.
2015-06-11 21:12:00 -04:00
func ( c * ChainInfo ) Output ( action Action , args ... string ) error {
2017-11-28 16:15:55 -05:00
iptable := GetIptable ( c . IPTable . Version )
2015-05-06 19:47:41 -04:00
a := [ ] string { "-t" , string ( c . Table ) , string ( action ) , "OUTPUT" }
if len ( args ) > 0 {
a = append ( a , args ... )
}
2017-11-28 16:15:55 -05:00
if output , err := iptable . Raw ( a ... ) ; err != nil {
2015-05-06 19:47:41 -04:00
return err
} else if len ( output ) != 0 {
return ChainError { Chain : "OUTPUT" , Output : output }
}
return nil
}
2015-05-18 19:49:12 -04:00
// Remove removes the chain.
2015-06-11 21:12:00 -04:00
func ( c * ChainInfo ) Remove ( ) error {
2017-11-28 16:15:55 -05:00
iptable := GetIptable ( c . IPTable . Version )
2015-05-06 19:47:41 -04:00
// Ignore errors - This could mean the chains were never set up
if c . Table == Nat {
2015-06-16 02:55:29 -04:00
c . Prerouting ( Delete , "-m" , "addrtype" , "--dst-type" , "LOCAL" , "-j" , c . Name )
2017-11-28 16:15:55 -05:00
c . Output ( Delete , "-m" , "addrtype" , "--dst-type" , "LOCAL" , "!" , "--dst" , iptable . LoopbackByVersion ( ) , "-j" , c . Name )
2015-06-16 02:55:29 -04:00
c . Output ( Delete , "-m" , "addrtype" , "--dst-type" , "LOCAL" , "-j" , c . Name ) // Created in versions <= 0.1.6
2015-05-06 19:47:41 -04:00
c . Prerouting ( Delete )
c . Output ( Delete )
}
2017-11-28 16:15:55 -05:00
iptable . Raw ( "-t" , string ( c . Table ) , "-F" , c . Name )
iptable . Raw ( "-t" , string ( c . Table ) , "-X" , c . Name )
2015-05-06 19:47:41 -04:00
return nil
}
2015-05-18 19:49:12 -04:00
// Exists checks if a rule exists
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) Exists ( table Table , chain string , rule ... string ) bool {
return iptable . exists ( false , table , chain , rule ... )
2016-10-05 03:04:05 -04:00
}
// ExistsNative behaves as Exists with the difference it
// will always invoke `iptables` binary.
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) ExistsNative ( table Table , chain string , rule ... string ) bool {
return iptable . exists ( true , table , chain , rule ... )
2016-10-05 03:04:05 -04:00
}
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) exists ( native bool , table Table , chain string , rule ... string ) bool {
f := iptable . Raw
2016-10-05 03:04:05 -04:00
if native {
2017-11-28 16:15:55 -05:00
f = iptable . raw
2016-10-05 03:04:05 -04:00
}
2015-05-06 19:47:41 -04:00
if string ( table ) == "" {
table = Filter
}
2017-03-08 13:37:13 -05:00
if err := initCheck ( ) ; err != nil {
// The exists() signature does not allow us to return an error, but at least
// we can skip the (likely invalid) exec invocation.
return false
}
2016-03-08 01:21:17 -05:00
2016-02-02 15:52:34 -05:00
if supportsCOpt {
// if exit status is 0 then return true, the rule exists
2016-10-05 03:04:05 -04:00
_ , err := f ( append ( [ ] string { "-t" , string ( table ) , "-C" , chain } , rule ... ) ... )
2016-02-02 15:52:34 -05:00
return err == nil
2015-05-06 19:47:41 -04:00
}
2016-02-02 15:52:34 -05:00
// parse "iptables -S" for the rule (it checks rules in a specific chain
// in a specific table and it is very unreliable)
2017-11-28 16:15:55 -05:00
return iptable . existsRaw ( table , chain , rule ... )
2016-02-02 15:52:34 -05:00
}
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) existsRaw ( table Table , chain string , rule ... string ) bool {
path := iptablesPath
if iptable . Version == IPv6 {
path = ip6tablesPath
}
2016-02-02 15:52:34 -05:00
ruleString := fmt . Sprintf ( "%s %s\n" , chain , strings . Join ( rule , " " ) )
2017-11-28 16:15:55 -05:00
existingRules , _ := exec . Command ( path , "-t" , string ( table ) , "-S" , chain ) . Output ( )
2015-05-06 19:47:41 -04:00
2015-05-19 18:28:48 -04:00
return strings . Contains ( string ( existingRules ) , ruleString )
2015-05-06 19:47:41 -04:00
}
2018-04-11 11:46:18 -04:00
// Maximum duration that an iptables operation can take
// before flagging a warning.
const opWarnTime = 2 * time . Second
func filterOutput ( start time . Time , output [ ] byte , args ... string ) [ ] byte {
// Flag operations that have taken a long time to complete
2018-04-26 10:19:26 -04:00
opTime := time . Since ( start )
if opTime > opWarnTime {
logrus . Warnf ( "xtables contention detected while running [%s]: Waited for %.2f seconds and received %q" , strings . Join ( args , " " ) , float64 ( opTime ) / float64 ( time . Second ) , string ( output ) )
2018-04-11 11:46:18 -04:00
}
// ignore iptables' message about xtables lock:
// it is a warning, not an error.
if strings . Contains ( string ( output ) , xLockWaitMsg ) {
output = [ ] byte ( "" )
}
// Put further filters here if desired
return output
}
2015-05-18 19:49:12 -04:00
// Raw calls 'iptables' system command, passing supplied arguments.
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) Raw ( args ... string ) ( [ ] byte , error ) {
2015-05-06 19:47:41 -04:00
if firewalldRunning {
2021-01-07 11:46:32 -05:00
// select correct IP version for firewalld
ipv := Iptables
if iptable . Version == IPv6 {
ipv = IP6Tables
}
2018-04-11 11:46:18 -04:00
startTime := time . Now ( )
2021-01-07 11:46:32 -05:00
output , err := Passthrough ( ipv , args ... )
2015-05-06 19:47:41 -04:00
if err == nil || ! strings . Contains ( err . Error ( ) , "was not provided by any .service files" ) {
2018-04-11 11:46:18 -04:00
return filterOutput ( startTime , output , args ... ) , err
2015-05-06 19:47:41 -04:00
}
}
2017-11-28 16:15:55 -05:00
return iptable . raw ( args ... )
2016-02-05 13:34:48 -05:00
}
2015-05-06 19:47:41 -04:00
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) raw ( args ... string ) ( [ ] byte , error ) {
2015-05-06 19:47:41 -04:00
if err := initCheck ( ) ; err != nil {
return nil , err
}
if supportsXlock {
args = append ( [ ] string { "--wait" } , args ... )
2015-05-12 23:27:22 -04:00
} else {
bestEffortLock . Lock ( )
defer bestEffortLock . Unlock ( )
2015-05-06 19:47:41 -04:00
}
2017-11-28 16:15:55 -05:00
path := iptablesPath
2020-12-11 05:10:55 -05:00
commandName := "iptables"
2017-11-28 16:15:55 -05:00
if iptable . Version == IPv6 {
path = ip6tablesPath
2020-12-11 05:10:55 -05:00
commandName = "ip6tables"
2017-11-28 16:15:55 -05:00
}
logrus . Debugf ( "%s, %v" , path , args )
2015-05-06 19:47:41 -04:00
2018-04-11 11:46:18 -04:00
startTime := time . Now ( )
2017-11-28 16:15:55 -05:00
output , err := exec . Command ( path , args ... ) . CombinedOutput ( )
2015-05-06 19:47:41 -04:00
if err != nil {
2020-12-11 05:10:55 -05:00
return nil , fmt . Errorf ( "iptables failed: %s %v: %s (%s)" , commandName , strings . Join ( args , " " ) , output , err )
2015-05-06 19:47:41 -04:00
}
2018-04-11 11:46:18 -04:00
return filterOutput ( startTime , output , args ... ) , err
2015-05-06 19:47:41 -04:00
}
2015-12-15 00:57:56 -05:00
2017-05-21 22:25:52 -04:00
// RawCombinedOutput internally calls the Raw function and returns a non nil
2015-12-15 00:57:56 -05:00
// error if Raw returned a non nil error or a non empty output
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) RawCombinedOutput ( args ... string ) error {
if output , err := iptable . Raw ( args ... ) ; err != nil || len ( output ) != 0 {
2015-12-15 00:57:56 -05:00
return fmt . Errorf ( "%s (%v)" , string ( output ) , err )
}
return nil
}
2015-09-15 04:30:10 -04:00
2016-02-05 13:34:48 -05:00
// RawCombinedOutputNative behave as RawCombinedOutput with the difference it
// will always invoke `iptables` binary
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) RawCombinedOutputNative ( args ... string ) error {
if output , err := iptable . raw ( args ... ) ; err != nil || len ( output ) != 0 {
2016-02-05 13:34:48 -05:00
return fmt . Errorf ( "%s (%v)" , string ( output ) , err )
}
return nil
}
2015-09-15 04:30:10 -04:00
// ExistChain checks if a chain exists
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) ExistChain ( chain string , table Table ) bool {
if _ , err := iptable . Raw ( "-t" , string ( table ) , "-nL" , chain ) ; err == nil {
2015-09-15 04:30:10 -04:00
return true
}
return false
}
2016-02-02 15:52:34 -05:00
2017-03-08 13:37:13 -05:00
// GetVersion reads the iptables version numbers during initialization
2016-02-02 15:52:34 -05:00
func GetVersion ( ) ( major , minor , micro int , err error ) {
2017-03-08 13:37:13 -05:00
out , err := exec . Command ( iptablesPath , "--version" ) . CombinedOutput ( )
2016-02-02 15:52:34 -05:00
if err == nil {
major , minor , micro = parseVersionNumbers ( string ( out ) )
}
return
}
2016-10-28 16:54:52 -04:00
// SetDefaultPolicy sets the passed default policy for the table/chain
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) SetDefaultPolicy ( table Table , chain string , policy Policy ) error {
if err := iptable . RawCombinedOutput ( "-t" , string ( table ) , "-P" , chain , string ( policy ) ) ; err != nil {
2016-10-28 16:54:52 -04:00
return fmt . Errorf ( "setting default policy to %v in %v chain failed: %v" , policy , chain , err )
}
return nil
}
2016-02-02 15:52:34 -05:00
func parseVersionNumbers ( input string ) ( major , minor , micro int ) {
re := regexp . MustCompile ( ` v\d*.\d*.\d* ` )
line := re . FindString ( input )
fmt . Sscanf ( line , "v%d.%d.%d" , & major , & minor , & micro )
return
}
// iptables -C, --check option was added in v.1.4.11
// http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
func supportsCOption ( mj , mn , mc int ) bool {
return mj > 1 || ( mj == 1 && ( mn > 4 || ( mn == 4 && mc >= 11 ) ) )
}
2017-03-13 03:46:46 -04:00
// AddReturnRule adds a return rule for the chain in the filter table
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) AddReturnRule ( chain string ) error {
2017-03-13 03:46:46 -04:00
var (
table = Filter
args = [ ] string { "-j" , "RETURN" }
)
2017-11-28 16:15:55 -05:00
if iptable . Exists ( table , chain , args ... ) {
2017-03-13 03:46:46 -04:00
return nil
}
2017-11-28 16:15:55 -05:00
err := iptable . RawCombinedOutput ( append ( [ ] string { "-A" , chain } , args ... ) ... )
2017-03-13 03:46:46 -04:00
if err != nil {
return fmt . Errorf ( "unable to add return rule in %s chain: %s" , chain , err . Error ( ) )
}
return nil
}
// EnsureJumpRule ensures the jump rule is on top
2017-11-28 16:15:55 -05:00
func ( iptable IPTable ) EnsureJumpRule ( fromChain , toChain string ) error {
2017-03-13 03:46:46 -04:00
var (
table = Filter
args = [ ] string { "-j" , toChain }
)
2017-11-28 16:15:55 -05:00
if iptable . Exists ( table , fromChain , args ... ) {
err := iptable . RawCombinedOutput ( append ( [ ] string { "-D" , fromChain } , args ... ) ... )
2017-03-13 03:46:46 -04:00
if err != nil {
return fmt . Errorf ( "unable to remove jump to %s rule in %s chain: %s" , toChain , fromChain , err . Error ( ) )
}
}
2017-11-28 16:15:55 -05:00
err := iptable . RawCombinedOutput ( append ( [ ] string { "-I" , fromChain } , args ... ) ... )
2017-03-13 03:46:46 -04:00
if err != nil {
return fmt . Errorf ( "unable to insert jump to %s rule in %s chain: %s" , toChain , fromChain , err . Error ( ) )
}
return nil
}