|
|
@@ -17,11 +17,12 @@ package iptables
|
|
|
import (
|
|
|
"fmt"
|
|
|
"net"
|
|
|
- "strings"
|
|
|
"sync"
|
|
|
"time"
|
|
|
|
|
|
"github.com/coreos/go-iptables/iptables"
|
|
|
+ "github.com/go-kit/kit/log"
|
|
|
+ "github.com/go-kit/kit/log/level"
|
|
|
)
|
|
|
|
|
|
// Protocol represents an IP protocol.
|
|
|
@@ -47,9 +48,11 @@ type Client interface {
|
|
|
AppendUnique(table string, chain string, rule ...string) error
|
|
|
Delete(table string, chain string, rule ...string) error
|
|
|
Exists(table string, chain string, rule ...string) (bool, error)
|
|
|
+ List(table string, chain string) ([]string, error)
|
|
|
ClearChain(table string, chain string) error
|
|
|
DeleteChain(table string, chain string) error
|
|
|
NewChain(table string, chain string) error
|
|
|
+ ListChains(table string) ([]string, error)
|
|
|
}
|
|
|
|
|
|
// Rule is an interface for interacting with iptables objects.
|
|
|
@@ -107,7 +110,17 @@ func (r *rule) String() string {
|
|
|
if r == nil {
|
|
|
return ""
|
|
|
}
|
|
|
- return fmt.Sprintf("%s_%s_%s", r.table, r.chain, strings.Join(r.spec, "_"))
|
|
|
+ spec := r.table + " -A " + r.chain
|
|
|
+ for i, s := range r.spec {
|
|
|
+ spec += " "
|
|
|
+ // If this is the content of a comment, wrap the value in quotes.
|
|
|
+ if i > 0 && r.spec[i-1] == "--comment" {
|
|
|
+ spec += `"` + s + `"`
|
|
|
+ } else {
|
|
|
+ spec += s
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return spec
|
|
|
}
|
|
|
|
|
|
func (r *rule) Proto() Protocol {
|
|
|
@@ -132,6 +145,7 @@ func NewIPv6Chain(table, name string) Rule {
|
|
|
}
|
|
|
|
|
|
func (c *chain) Add(client Client) error {
|
|
|
+ // Note: `ClearChain` creates a chain if it does not exist.
|
|
|
if err := client.ClearChain(c.table, c.chain); err != nil {
|
|
|
return fmt.Errorf("failed to add iptables chain: %v", err)
|
|
|
}
|
|
|
@@ -171,41 +185,73 @@ func (c *chain) String() string {
|
|
|
if c == nil {
|
|
|
return ""
|
|
|
}
|
|
|
- return fmt.Sprintf("%s_%s", c.table, c.chain)
|
|
|
+ return chainToString(c.table, c.chain)
|
|
|
}
|
|
|
|
|
|
func (c *chain) Proto() Protocol {
|
|
|
return c.proto
|
|
|
}
|
|
|
|
|
|
+func chainToString(table, chain string) string {
|
|
|
+ return fmt.Sprintf("%s -N %s", table, chain)
|
|
|
+}
|
|
|
+
|
|
|
// Controller is able to reconcile a given set of iptables rules.
|
|
|
type Controller struct {
|
|
|
v4 Client
|
|
|
v6 Client
|
|
|
errors chan error
|
|
|
+ logger log.Logger
|
|
|
|
|
|
sync.Mutex
|
|
|
rules []Rule
|
|
|
subscribed bool
|
|
|
}
|
|
|
|
|
|
-// New generates a new iptables rules controller.
|
|
|
-// It expects an IP address length to determine
|
|
|
-// whether to operate in IPv4 or IPv6 mode.
|
|
|
-func New() (*Controller, error) {
|
|
|
- v4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to create iptables IPv4 client: %v", err)
|
|
|
+// ControllerOption modifies the controller's configuration.
|
|
|
+type ControllerOption func(h *Controller)
|
|
|
+
|
|
|
+// WithLogger adds a logger to the controller.
|
|
|
+func WithLogger(logger log.Logger) ControllerOption {
|
|
|
+ return func(c *Controller) {
|
|
|
+ c.logger = logger
|
|
|
}
|
|
|
- v6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to create iptables IPv6 client: %v", err)
|
|
|
+}
|
|
|
+
|
|
|
+// WithClients adds iptables clients to the controller.
|
|
|
+func WithClients(v4, v6 Client) ControllerOption {
|
|
|
+ return func(c *Controller) {
|
|
|
+ c.v4 = v4
|
|
|
+ c.v6 = v6
|
|
|
}
|
|
|
- return &Controller{
|
|
|
- v4: v4,
|
|
|
- v6: v6,
|
|
|
+}
|
|
|
+
|
|
|
+// New generates a new iptables rules controller.
|
|
|
+// If no options are given, IPv4 and IPv6 clients
|
|
|
+// will be instantiated using the regular iptables backend.
|
|
|
+func New(opts ...ControllerOption) (*Controller, error) {
|
|
|
+ c := &Controller{
|
|
|
errors: make(chan error),
|
|
|
- }, nil
|
|
|
+ logger: log.NewNopLogger(),
|
|
|
+ }
|
|
|
+ for _, o := range opts {
|
|
|
+ o(c)
|
|
|
+ }
|
|
|
+ if c.v4 == nil {
|
|
|
+ v4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("failed to create iptables IPv4 client: %v", err)
|
|
|
+ }
|
|
|
+ c.v4 = v4
|
|
|
+ }
|
|
|
+ if c.v6 == nil {
|
|
|
+ v6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("failed to create iptables IPv6 client: %v", err)
|
|
|
+ }
|
|
|
+ c.v6 = v6
|
|
|
+ }
|
|
|
+ return c, nil
|
|
|
}
|
|
|
|
|
|
// Run watches for changes to iptables rules and reconciles
|
|
|
@@ -223,7 +269,7 @@ func (c *Controller) Run(stop <-chan struct{}) (<-chan error, error) {
|
|
|
defer close(c.errors)
|
|
|
for {
|
|
|
select {
|
|
|
- case <-time.After(5 * time.Second):
|
|
|
+ case <-time.After(30 * time.Second):
|
|
|
case <-stop:
|
|
|
return
|
|
|
}
|
|
|
@@ -242,12 +288,14 @@ func (c *Controller) Run(stop <-chan struct{}) (<-chan error, error) {
|
|
|
func (c *Controller) reconcile() error {
|
|
|
c.Lock()
|
|
|
defer c.Unlock()
|
|
|
+ var rc ruleCache
|
|
|
for i, r := range c.rules {
|
|
|
- ok, err := r.Exists(c.client(r.Proto()))
|
|
|
+ ok, err := rc.exists(c.client(r.Proto()), r)
|
|
|
if err != nil {
|
|
|
return fmt.Errorf("failed to check if rule exists: %v", err)
|
|
|
}
|
|
|
if !ok {
|
|
|
+ level.Info(c.logger).Log("msg", fmt.Sprintf("applying %d iptables rules", len(c.rules)-i))
|
|
|
if err := c.resetFromIndex(i, c.rules); err != nil {
|
|
|
return fmt.Errorf("failed to add rule: %v", err)
|
|
|
}
|