@ -7,6 +7,7 @@ package linuxfw
import (
"bytes"
"errors"
"fmt"
"net/netip"
"os"
@ -20,6 +21,14 @@ import (
"tailscale.com/util/multierr"
)
// isNotExistError needs to be overridden in tests that rely on distinguishing
// this error, because we don't have a good way how to create a new
// iptables.Error of that type.
var isNotExistError = func ( err error ) bool {
var e * iptables . Error
return errors . As ( err , & e ) && e . IsNotExist ( )
}
type iptablesInterface interface {
// Adding this interface for testing purposes so we can mock out
// the iptables library, in reality this is a wrapper to *iptables.IPTables.
@ -156,10 +165,6 @@ func (i *iptablesRunner) HasIPV6NAT() bool {
return i . v6NATAvailable
}
func isErrChainNotExist ( err error ) bool {
return errCode ( err ) == 1
}
// getIPTByAddr returns the iptablesInterface with correct IP family
// that we will be using for the given address.
func ( i * iptablesRunner ) getIPTByAddr ( addr netip . Addr ) iptablesInterface {
@ -261,7 +266,7 @@ func (i *iptablesRunner) AddChains() error {
// If the chain already exists, it is a no-op.
create := func ( ipt iptablesInterface , table , chain string ) error {
err := ipt . ClearChain ( table , chain )
if is ErrChain NotExist( err ) {
if is NotExistError ( err ) {
// nonexistent chain. let's create it!
return ipt . NewChain ( table , chain )
}
@ -377,7 +382,7 @@ func (i *iptablesRunner) DNATNonTailscaleTraffic(tun string, dst netip.Addr) err
// originDst to the backend dsts. Traffic will be load balanced using round robin.
func ( i * iptablesRunner ) DNATWithLoadBalancer ( origDst netip . Addr , dsts [ ] netip . Addr ) error {
table := i . getIPTByAddr ( dsts [ 0 ] )
if err := table . ClearChain ( "nat" , "PREROUTING" ) ; err != nil && ! is ErrChain NotExist( err ) {
if err := table . ClearChain ( "nat" , "PREROUTING" ) ; err != nil && ! is NotExistError ( err ) {
// If clearing the PREROUTING chain fails, fail the whole operation. This
// rule is currently only used in Kubernetes containers where a
// failed container gets restarted which should hopefully fix things.
@ -454,7 +459,7 @@ func (i *iptablesRunner) DelChains() error {
func ( i * iptablesRunner ) DelBase ( ) error {
del := func ( ipt iptablesInterface , table , chain string ) error {
if err := ipt . ClearChain ( table , chain ) ; err != nil {
if is ErrChain NotExist( err ) {
if is NotExistError ( err ) {
// nonexistent chain. That's fine, since it's
// the desired state anyway.
return nil
@ -602,14 +607,8 @@ func IPTablesCleanUp(logf logger.Logf) {
func delTSHook ( ipt iptablesInterface , table , chain string , logf logger . Logf ) error {
tsChain := tsChain ( chain )
args := [ ] string { "-j" , tsChain }
if err := ipt . Delete ( table , chain , args ... ) ; err != nil {
// TODO(apenwarr): check for errCode(1) here.
// Unfortunately the error code from the iptables
// module resists unwrapping, unlike with other
// calls. So we have to assume if Delete fails,
// it's because there is no such rule.
logf ( "deleting %v in %s/%s: %v" , args , table , chain , err )
return nil
if err := ipt . Delete ( table , chain , args ... ) ; err != nil && ! isNotExistError ( err ) {
return fmt . Errorf ( "deleting %v in %s/%s: %v" , args , table , chain , err )
}
return nil
}
@ -618,7 +617,7 @@ func delTSHook(ipt iptablesInterface, table, chain string, logf logger.Logf) err
// since the desired state is already achieved. otherwise, it returns an error.
func delChain ( ipt iptablesInterface , table , chain string ) error {
if err := ipt . ClearChain ( table , chain ) ; err != nil {
if is ErrChain NotExist( err ) {
if is NotExistError ( err ) {
// nonexistent chain. nothing to do.
return nil
}