package firewall

import (
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"

	utilexec "github.com/romana/core/agent/exec"
	"github.com/romana/core/common"
	"github.com/romana/core/common/log/trace"

	log "github.com/romana/rlog"
)

type IPtables struct {
	chains        []IPtablesChain
	u32filter     string
	chainPrefix   string
	interfaceName string
	Store         firewallStore
	os            utilexec.Executable
	initialized   bool

	networkConfig NetConfig
}

func (fw *IPtables) Init(utilexec utilexec.Executable, store FirewallStore, nc NetConfig) error {
	var err error

	fwstore := firewallStore{}
	fwstore.db = store.GetDb()
	fwstore.mu = store.GetMutex()

	fw.Store = fwstore
	fw.os = utilexec
	fw.networkConfig = nc

	if iptablesCmd, err = exec.LookPath("iptables"); err != nil {
		return err
	}

	return nil
}

func (fw *IPtables) SetEndpoint(netif FirewallEndpoint) error {
	err := fw.makeRules(netif)
	if err != nil {
		return fmt.Errorf("In Firewall.Init() error %s", err)
	}

	fw.initialized = true
	return err
}

func (fw IPtables) Metadata() map[string]interface{} {
	var chains []string
	for _, chain := range fw.chains {
		chains = append(chains, chain.ChainName)
	}

	metadata := make(map[string]interface{})
	metadata["provider"] = fw.Provider()
	metadata["chainPrefix"] = fw.chainPrefix
	metadata["chains"] = chains
	metadata["u32filter"] = fw.u32filter

	return metadata
}

func (fw IPtables) Provider() string {
	return "iptables"
}

func (fw *IPtables) SetDefaultRules(rules []FirewallRule) error {
	for _, rule := range rules {
		log.Infof("In SetDefaultRules() processing rule %s", rule.GetBody())
		if rule.GetType() == "iptables" {
			iptablesRule := &IPtablesRule{
				Body:  rule.GetBody(),
				State: setRuleInactive.String(),
			}

			err := fw.injectRule(iptablesRule)
			if err != nil {
				return fmt.Errorf("In SetDefaultRules() failed to set the rule %s, %s", rule.GetBody(), err)
			}

		} else {
			return fmt.Errorf("In SetDefaultRules() unsupported rule type %s", rule.GetType())
		}
	}

	return nil
}

func (fw *IPtables) injectRule(rule *IPtablesRule) error {
	rule2arr := strings.Split(rule.GetBody(), " ")
	if len(rule2arr) < 3 {
		return fmt.Errorf("In injectRule() not enough elements in rule %s", rule.GetBody())
	}

	ruleChain := rule2arr[0]

	for i, chain := range fw.chains {
		if chain.ChainName == ruleChain {
			fw.chains[i].Rules = append(fw.chains[i].Rules, rule)
			log.Infof("In injectRule() adding rule %s into chain %s", rule.GetBody(), chain.ChainName)
			return nil
		}
	}

	return fmt.Errorf("In injectRule() firewall doesn't manage chain for rule %s", rule.GetBody())
}

type IPtablesChain struct {
	BaseChain  string
	Directions []string
	Rules      []*IPtablesRule
	ChainName  string
}

func NewIPtablesChain(baseChain string, direction []string, rules []*IPtablesRule, chainName string) *IPtablesChain {
	return &IPtablesChain{baseChain, direction, rules, chainName}
}

func (fw *IPtables) makeRules(netif FirewallEndpoint) error {
	log.Infof("In makeRules() with %s", netif.GetName())

	var err error
	fw.u32filter, fw.chainPrefix, err = fw.prepareU32Rules(netif.GetIP())
	if err != nil {

		return err
	}
	fw.interfaceName = netif.GetName()

	fw.chains = append(fw.chains, IPtablesChain{
		BaseChain:  "INPUT",
		Directions: []string{"i"},
		ChainName:  "ROMANA-INPUT",
	})
	fw.chains = append(fw.chains, IPtablesChain{
		BaseChain:  "OUTPUT",
		Directions: []string{"o"},
		ChainName:  "ROMANA-FORWARD-IN",
	})
	fw.chains = append(fw.chains, IPtablesChain{
		BaseChain:  "FORWARD",
		Directions: []string{"i"},
		ChainName:  "ROMANA-FORWARD-OUT",
	})
	fw.chains = append(fw.chains, IPtablesChain{
		BaseChain:  "FORWARD",
		Directions: []string{"o"},

		ChainName: "ROMANA-FORWARD-IN",
	})

	log.Infof("In makeRules() created chains %v", fw.chains)
	return nil
}

func (fw *IPtables) isChainExistByName(chainName string) bool {
	args := []string{"-w", "-L", chainName}
	output, err := fw.os.Exec(iptablesCmd, args)
	if err != nil {
		log.Infof("isChainExist(): iptables -L %s returned %s", chainName, err)
		return false
	}
	log.Infof("isChainExist(): iptables -L %s returned %s", chainName, string(output))
	return true
}

func (fw *IPtables) isRuleExist(rule FirewallRule) bool {
	body := strings.Split(rule.GetBody(), " ")
	args := append([]string{"-w", "-C"}, body...)
	_, err := fw.os.Exec(iptablesCmd, args)
	if err != nil {
		return false
	}
	return true
}

func (fw *IPtables) ensureIPtablesChain(chainName string, opType chainState) error {
	log.Infof("In ensureIPtablesChain(): %s %s", opType.String(), chainName)

	log.Tracef(trace.Inside, "In ensureIPtablesChain(): Testing chain ", chainName)
	exists := fw.isChainExistByName(chainName)
	log.Tracef(trace.Inside, "In ensureIPtablesChain(): Test for chain %s returned %b", chainName, exists)

	var iptablesAction string
	switch opType {
	case ensureChainExists:
		if exists {
			log.Tracef(trace.Inside, "In ensureIPtablesChain(): nothing to do for chain %s", chainName)
			return nil
		} else {
			iptablesAction = "-N"
		}

	case ensureChainAbsent:
		if exists {
			iptablesAction = "-D"
		} else {
			log.Tracef(trace.Inside, "In ensureIPtablesChain(): nothing to do for chain %s", chainName)
			return nil
		}
	}

	args := []string{iptablesAction, chainName}
	_, err := fw.os.Exec(iptablesCmd, args)
	return err
}

func (fw *IPtables) CreateChains(chains []IPtablesChain) error {
	for _, chain := range chains {
		fw.ensureIPtablesChain(chain.ChainName, ensureChainExists)
	}
	return nil
}

type opDivertTrafficAction int

const (
	installDivertRules opDivertTrafficAction = iota
	removeDivertRules
)

func (d opDivertTrafficAction) String() string {
	var result string
	switch d {
	case installDivertRules:
		result = "Installing divert rules"
	case removeDivertRules:
		result = "Removing divert rules"
	}
	return result
}

func (fw *IPtables) DivertTrafficToRomanaIPtablesChain(chain IPtablesChain, opType opDivertTrafficAction) error {

	log.Infof("In DivertTrafficToRomanaIPtablesChain() processing chain %v with state %s", chain, opType)

	var state RuleState
	switch opType {
	case installDivertRules:
		state = EnsureLast
	case removeDivertRules:
		state = EnsureAbsent
	}

	for _, directionLiteral := range chain.Directions {
		direction := fmt.Sprintf("-%s", directionLiteral)
		body := fmt.Sprintf("%s %s %s %s %s", chain.BaseChain, direction, fw.interfaceName, "-j", chain.ChainName)
		rule := &IPtablesRule{
			Body:  body,
			State: setRuleInactive.String(),
		}

		err0 := fw.addIPtablesRule(rule)
		if err0 != nil {
			log.Errorf("In DivertTrafficToRomanaIPtablesChain() failed to process chain %v", chain)
			return err0
		}

		if err1 := fw.EnsureRule(rule, state); err1 != nil {
			log.Errorf("In DivertTrafficToRomanaIPtablesChain() failed to process chain %v", chain)
			return err1
		}

		if err2 := fw.Store.switchIPtablesRule(rule, setRuleActive); err2 != nil {
			log.Error("In DivertTrafficToRomanaIPtablesChain() iptables rule created but activation failed ", rule.GetBody())
			return err2
		}

	}
	log.Info("DivertTrafficToRomanaIPtablesChain() successfully processed chain number", chain)
	return nil
}

func (fw *IPtables) addIPtablesRule(rule *IPtablesRule) error {
	if err := fw.Store.addIPtablesRule(rule); err != nil {
		log.Error("In addIPtablesRule failed to add ", rule.GetBody())
		return err
	}

	return nil
}

func (fw *IPtables) CreateRules(chain int) error {
	log.Info("In CreateRules() for chain", chain)
	for _, rule := range fw.chains[chain].Rules {

		err0 := fw.addIPtablesRule(rule)
		if err0 != nil {
			log.Error("In CreateRules() create db record for iptables rule ", rule.GetBody())
			return err0
		}

		err1 := fw.EnsureRule(rule, EnsureFirst)
		if err1 != nil {
			log.Error("In CreateRules() failed to create install firewall rule ", rule.GetBody())
			return err1
		}

		if err2 := fw.Store.switchIPtablesRule(rule, setRuleActive); err2 != nil {
			log.Error("In CreateRules() iptables rule created, but activation failed ", rule.GetBody())
			return err2
		}
	}
	log.Info("Creating firewall rules success")
	return nil
}

func (fw *IPtables) CreateU32Rules(chain int) error {
	log.Info("Creating U32 firewall rules for chain", chain)
	chainName := fw.chains[chain].ChainName
	args := []string{"-w", "-A", chainName, "-m", "u32", "--u32", fw.u32filter, "-j", "ACCEPT"}
	_, err := fw.os.Exec(iptablesCmd, args)
	if err != nil {
		log.Error("Creating U32 firewall rules failed")
		return err
	}
	log.Info("Creating U32 firewall rules success")
	return nil
}

func (fw *IPtables) CreateDefaultDropRule(chain int) error {
	return fw.CreateDefaultRule(chain, targetDrop)
}

func (fw *IPtables) CreateDefaultRule(chain int, target string) error {
	log.Infof("In CreateDefaultRule() %s rules for chain %d", target, chain)
	chainName := fw.chains[chain].ChainName
	body := fmt.Sprintf("%s %s %s", chainName, "-j", target)
	rule := &IPtablesRule{
		Body:  body,
		State: setRuleInactive.String(),
	}

	err0 := fw.addIPtablesRule(rule)
	if err0 != nil {
		log.Error("In CreateDefaultRule() create db record for iptables rule ", rule.GetBody())
		return err0
	}

	err1 := fw.EnsureRule(rule, EnsureLast)
	if err1 != nil {
		log.Errorf("In CreateDefaultRule() %s rules failed", target)
		return err1
	}

	if err2 := fw.Store.switchIPtablesRule(rule, setRuleActive); err2 != nil {
		log.Error("In CreateDefaultRule() iptables rule created but activation failed ", rule.GetBody())
		return err2
	}

	log.Info("In CreateDefaultRule() success")
	return nil
}

func (fw *IPtables) prepareTenantSegmentMask() uint64 {
	var res uint64
	tenantBits := fw.networkConfig.TenantBits()
	segmentBits := fw.networkConfig.SegmentBits()
	combinedTSBits := tenantBits + segmentBits
	endpointBits := fw.networkConfig.EndpointBits()
	res = ((1 << combinedTSBits) - 1) << endpointBits
	return res
}

func ipToInt(ip net.IP) uint64 {
	return uint64(ip[12])<<24 | uint64(ip[13])<<16 | uint64(ip[14])<<8 | uint64(ip[15])
}

func (fw *IPtables) RomanaNetNetmaskInt() (uint64, error) {
	cidr, err := fw.networkConfig.PNetCIDR()
	if err != nil {
		return 0, err
	}
	pNetMaskInt, err := MaskToInt(cidr.Mask)
	if err != nil {
		return 0, err
	}
	return pNetMaskInt, nil
}

func MaskToInt(mask net.IPMask) (uint64, error) {
	var imask uint64
	m, err := strconv.ParseInt(mask.String(), 16, 64)
	imask = uint64(m)
	if err != nil {
		return 0, err
	}
	return imask, nil
}

func (fw *IPtables) prepareU32Rules(ipAddr net.IP) (string, string, error) {
	fullMask, err := fw.prepareNetmaskBits()
	if err != nil {
		return "", "", err
	}
	addr := ipToInt(ipAddr)
	if err != nil {
		return "", "", err
	}
	filter1 := fmt.Sprintf("0x%X=0x%X", fullMask, addr&fullMask)
	filter := fmt.Sprintf("12&%s&&16&%s", filter1, filter1)
	tenantID := fw.extractTenantID(addr)
	segmentID := fw.extractSegmentID(addr)
	chainPrefix := fmt.Sprintf("ROMANA-T%dS%d-", tenantID, segmentID)
	return filter, chainPrefix, nil
}

func (fw *IPtables) prepareNetmaskBits() (uint64, error) {
	iCidrMask, err := fw.RomanaNetNetmaskInt()
	if err != nil {
		return 0, err
	}
	combinedTSMask := fw.prepareTenantSegmentMask()
	res := iCidrMask | combinedTSMask
	return res, nil
}

func (fw *IPtables) extractSegmentID(addr uint64) uint64 {
	endpointBits := fw.networkConfig.EndpointBits()
	segmentBits := fw.networkConfig.SegmentBits()
	sid := (addr >> endpointBits) & ((1 << segmentBits) - 1)
	return sid
}

func (fw *IPtables) extractTenantID(addr uint64) uint64 {
	endpointBits := fw.networkConfig.EndpointBits()
	segmentBits := fw.networkConfig.SegmentBits()
	tenantBits := fw.networkConfig.TenantBits()
	tid := (addr >> (endpointBits + segmentBits)) & ((1 << tenantBits) - 1)
	return tid
}

func (fw IPtables) ProvisionEndpoint() error {
	log.Info("In ProvisionEndpoint() with firewall.")

	if !fw.initialized {
		return fmt.Errorf("In ProvisionEndpoint(), can not provision uninitialized firewall, use Init()")
	}

	err := fw.CreateChains(fw.chains)
	if err != nil {
		return err
	}

	log.Info("Firewall: creating rules")
	for chain := range fw.chains {
		if err := fw.CreateRules(chain); err != nil {
			return err
		}
	}

	if err := fw.CreateDefaultDropRule(ForwardOutChainIndex); err != nil {
		return err
	}

	log.Info("Firewall: diverting traffic")
	for _, chain := range fw.chains {
		if err := fw.DivertTrafficToRomanaIPtablesChain(chain, installDivertRules); err != nil {
			return err
		}
	}

	return nil
}

func (fw IPtables) Cleanup(netif FirewallEndpoint) error {
	if err := fw.deleteIPtablesRulesBySubstring(netif.GetName()); err != nil {
		log.Errorf("In Cleanup() failed to clean firewall for %s", netif.GetName())
		return err
	}
	return nil
}

func (fw *IPtables) deleteIPtablesRulesBySubstring(substring string) error {
	if substring == "" {
		return common.NewError("Empty substring specified to deleteIPtablesRulesBySubstring")
	}
	rules, err := fw.Store.findIPtablesRules(substring)
	if err != nil {
		return err
	}

	for _, rule := range *rules {
		if rule.State == setRuleInactive.String() {
			continue
		}

		err = fw.deleteIPtablesRule(&rule)
		if err != nil {
			return err
		}
	}

	return nil
}

func (fw *IPtables) deleteIPtablesRule(rule *IPtablesRule) error {
	if err := fw.Store.switchIPtablesRule(rule, setRuleInactive); err != nil {
		log.Error("In deleteIPtablesRule() failed to deactivate the rule", rule.GetBody())
		return err
	}

	if err1 := fw.EnsureRule(rule, EnsureAbsent); err1 != nil {
		log.Errorf("In deleteIPtablesRule() rule %s set inactive but failed to uninstall", rule.GetBody())
		return err1
	}

	if err2 := fw.Store.deleteIPtablesRule(rule); err2 != nil {
		log.Errorf("In deleteIPtablesRule() rule %s set inactive and uninstalled but failed to delete DB record", rule.GetBody())
		return err2
	}
	return nil
}

func (fw IPtables) EnsureRule(rule FirewallRule, opType RuleState) error {
	ruleExists := fw.isRuleExist(rule)

	args := []string{}
	if ruleExists {

		switch opType {
		case EnsureAbsent:
			args = append(args, "-D")
		default:
			log.Info("In EnsureRule - nothing to do ", rule.GetBody())
			return nil
		}
	} else {

		switch opType {
		case EnsureLast:
			args = append(args, "-A")
		case EnsureFirst:
			args = append(args, "-I")
		default:
			log.Info("In EnsureRule - nothing to do ", rule.GetBody())
			return nil
		}
	}

	args = append(args, strings.Split(rule.GetBody(), " ")...)
	cmdStr := iptablesCmd + " " + strings.Join(args, " ")
	out, err := fw.os.Exec(iptablesCmd, args)
	if err != nil {
		log.Errorf("[%s]: %s failed for rule %s with error %v, saying [%s]", cmdStr, opType, rule.GetBody(), err, string(out))
	} else {
		if out != nil && len(out) > 0 {
			log.Infof("%s success %s: [%s]", opType, rule.GetBody(), string(out))
		} else {
			log.Infof("%s success %s", opType, rule.GetBody())
		}
	}

	return err
}

func (fw IPtables) ListRules() ([]IPtablesRule, error) {
	return fw.Store.listIPtablesRules()
}
// Copyright (c) 2016 Pani Networks
// All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
// IPtables based firewall implementations.

package firewall

import (
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"

	utilexec "github.com/romana/core/agent/exec"
	"github.com/romana/core/common"
	"github.com/romana/core/common/log/trace"

	log "github.com/romana/rlog"
)

// IPtables implements romana Firewall using iptables.
type IPtables struct {
	chains        []IPtablesChain
	u32filter     string
	chainPrefix   string
	interfaceName string
	Store         firewallStore
	os            utilexec.Executable
	initialized   bool

	// Discovered run-time configuration.
	networkConfig NetConfig
}

// Init implements Firewall interface.
func (fw *IPtables) Init(utilexec utilexec.Executable, store FirewallStore, nc NetConfig) error {
	var err error

	fwstore := firewallStore{}
	fwstore.db = store.GetDb()
	fwstore.mu = store.GetMutex()

	fw.Store = fwstore
	fw.os = utilexec
	fw.networkConfig = nc

	if iptablesCmd, err = exec.LookPath("iptables"); err != nil {
		return err
	}

	return nil
}

// SetEndpoint implements Firewall interface.
func (fw *IPtables) SetEndpoint(netif FirewallEndpoint) error {
	err := fw.makeRules(netif)
	if err != nil {
		return fmt.Errorf("In Firewall.Init() error %s", err)
	}

	fw.initialized = true
	return err
}

// Metadata implements Firewall interface.
func (fw IPtables) Metadata() map[string]interface{} {
	var chains []string
	for _, chain := range fw.chains {
		chains = append(chains, chain.ChainName)
	}

	metadata := make(map[string]interface{})
	metadata["provider"] = fw.Provider()
	metadata["chainPrefix"] = fw.chainPrefix
	metadata["chains"] = chains
	metadata["u32filter"] = fw.u32filter

	return metadata
}

// Provider implements Firewall interface.
func (fw IPtables) Provider() string {
	return "iptables"
}

// SetDefaultRules implements Firewall interface.
func (fw *IPtables) SetDefaultRules(rules []FirewallRule) error {
	for _, rule := range rules {
		log.Infof("In SetDefaultRules() processing rule %s", rule.GetBody())
		if rule.GetType() == "iptables" {
			iptablesRule := &IPtablesRule{
				Body:  rule.GetBody(),
				State: setRuleInactive.String(),
			}

			err := fw.injectRule(iptablesRule)
			if err != nil {
				return fmt.Errorf("In SetDefaultRules() failed to set the rule %s, %s", rule.GetBody(), err)
			}

		} else {
			return fmt.Errorf("In SetDefaultRules() unsupported rule type %s", rule.GetType())
		}
	}

	return nil
}

// injectRule extracts chain name from a rule body and appends a rule
// to the appropriate chain in IPtables.chains list.
func (fw *IPtables) injectRule(rule *IPtablesRule) error {
	rule2arr := strings.Split(rule.GetBody(), " ")
	if len(rule2arr) < 3 {
		return fmt.Errorf("In injectRule() not enough elements in rule %s", rule.GetBody())
	}

	ruleChain := rule2arr[0]

	for i, chain := range fw.chains {
		if chain.ChainName == ruleChain {
			fw.chains[i].Rules = append(fw.chains[i].Rules, rule)
			log.Infof("In injectRule() adding rule %s into chain %s", rule.GetBody(), chain.ChainName)
			return nil
		}
	}

	// TODO should we create new chain instead of throwing error?
	return fmt.Errorf("In injectRule() firewall doesn't manage chain for rule %s", rule.GetBody())
}

// IPtablesChain describes state of the particular firewall chain.
type IPtablesChain struct {
	BaseChain  string
	Directions []string
	Rules      []*IPtablesRule
	ChainName  string
}

// NewIPtablesChain initializes a new firewall chain.
func NewIPtablesChain(baseChain string, direction []string, rules []*IPtablesRule, chainName string) *IPtablesChain {
	return &IPtablesChain{baseChain, direction, rules, chainName}
}

// makeRules extracts data from netif and initializes tenant/segment
// specific fields of IPtables struct.
func (fw *IPtables) makeRules(netif FirewallEndpoint) error {
	log.Infof("In makeRules() with %s", netif.GetName())

	var err error
	fw.u32filter, fw.chainPrefix, err = fw.prepareU32Rules(netif.GetIP())
	if err != nil {
		// TODO need personalized error here, or even panic
		return err
	}
	fw.interfaceName = netif.GetName()

	fw.chains = append(fw.chains, IPtablesChain{
		BaseChain:  "INPUT",
		Directions: []string{"i"},
		ChainName:  "ROMANA-INPUT",
	})
	fw.chains = append(fw.chains, IPtablesChain{
		BaseChain:  "OUTPUT",
		Directions: []string{"o"},
		ChainName:  "ROMANA-FORWARD-IN",
	})
	fw.chains = append(fw.chains, IPtablesChain{
		BaseChain:  "FORWARD",
		Directions: []string{"i"},
		ChainName:  "ROMANA-FORWARD-OUT",
	})
	fw.chains = append(fw.chains, IPtablesChain{
		BaseChain:  "FORWARD",
		Directions: []string{"o"},
		// Using ROMANA-FORWARD-IN second time to capture both
		// traffic from host to endpoint and
		// traffic from endpoint to another endpoint.
		ChainName: "ROMANA-FORWARD-IN",
	})

	log.Infof("In makeRules() created chains %v", fw.chains)
	return nil
}

// isChainExistByName verifies if given iptables chain exists.
// Returns true if chain exists.
func (fw *IPtables) isChainExistByName(chainName string) bool {
	args := []string{"-w", "-L", chainName}
	output, err := fw.os.Exec(iptablesCmd, args)
	if err != nil {
		log.Infof("isChainExist(): iptables -L %s returned %s", chainName, err)
		return false
	}
	log.Infof("isChainExist(): iptables -L %s returned %s", chainName, string(output))
	return true
}

// isRuleExist verifies if given iptables rule exists.
// Returns true if rule exists.
func (fw *IPtables) isRuleExist(rule FirewallRule) bool {
	body := strings.Split(rule.GetBody(), " ")
	args := append([]string{"-w", "-C"}, body...)
	_, err := fw.os.Exec(iptablesCmd, args)
	if err != nil {
		return false
	}
	return true
}

// ensureIPtablesChain checks if iptables chain in a desired state.
func (fw *IPtables) ensureIPtablesChain(chainName string, opType chainState) error {
	log.Infof("In ensureIPtablesChain(): %s %s", opType.String(), chainName)

	log.Tracef(trace.Inside, "In ensureIPtablesChain(): Testing chain ", chainName)
	exists := fw.isChainExistByName(chainName)
	log.Tracef(trace.Inside, "In ensureIPtablesChain(): Test for chain %s returned %b", chainName, exists)

	var iptablesAction string
	switch opType {
	case ensureChainExists:
		if exists {
			log.Tracef(trace.Inside, "In ensureIPtablesChain(): nothing to do for chain %s", chainName)
			return nil
		} else {
			iptablesAction = "-N"
		}

	case ensureChainAbsent:
		if exists {
			iptablesAction = "-D"
		} else {
			log.Tracef(trace.Inside, "In ensureIPtablesChain(): nothing to do for chain %s", chainName)
			return nil
		}
	}

	args := []string{iptablesAction, chainName}
	_, err := fw.os.Exec(iptablesCmd, args)
	return err
}

// CreateChains creates IPtables chains such as
// ROMANA-T0S0-OUTPUT, ROMANA-T0S0-FORWARD, ROMANA-T0S0-INPUT.
func (fw *IPtables) CreateChains(chains []IPtablesChain) error {
	for _, chain := range chains {
		fw.ensureIPtablesChain(chain.ChainName, ensureChainExists)
	}
	return nil
}

// opDivertTrafficAction is a parameter for DivertTrafficToRomanaIPtablesChain
// functions that indicates action to be taken.
type opDivertTrafficAction int

const (
	installDivertRules opDivertTrafficAction = iota
	removeDivertRules
)

func (d opDivertTrafficAction) String() string {
	var result string
	switch d {
	case installDivertRules:
		result = "Installing divert rules"
	case removeDivertRules:
		result = "Removing divert rules"
	}
	return result
}

// DivertTrafficToRomanaIPtablesChain injects iptables Rules to send traffic
// into the ROMANA chain.
// We need to do this for each tenant/segment pair as each pair will have different chain name.
func (fw *IPtables) DivertTrafficToRomanaIPtablesChain(chain IPtablesChain, opType opDivertTrafficAction) error {
	// Should be like that
	// iptables -A INPUT -i tap1234 -j ROMANA-T0S1-INPUT
	log.Infof("In DivertTrafficToRomanaIPtablesChain() processing chain %v with state %s", chain, opType)

	var state RuleState
	switch opType {
	case installDivertRules:
		state = EnsureLast
	case removeDivertRules:
		state = EnsureAbsent
	}

	// baseChain := fw.chains[chain].BaseChain
	for _, directionLiteral := range chain.Directions {
		direction := fmt.Sprintf("-%s", directionLiteral)
		body := fmt.Sprintf("%s %s %s %s %s", chain.BaseChain, direction, fw.interfaceName, "-j", chain.ChainName)
		rule := &IPtablesRule{
			Body:  body,
			State: setRuleInactive.String(),
		}

		// First create rule record in database.
		err0 := fw.addIPtablesRule(rule)
		if err0 != nil {
			log.Errorf("In DivertTrafficToRomanaIPtablesChain() failed to process chain %v", chain)
			return err0
		}

		// Then create actuall rule in the system.
		if err1 := fw.EnsureRule(rule, state); err1 != nil {
			log.Errorf("In DivertTrafficToRomanaIPtablesChain() failed to process chain %v", chain)
			return err1
		}

		// Finally, set 'active' flag in database record.
		if err2 := fw.Store.switchIPtablesRule(rule, setRuleActive); err2 != nil {
			log.Error("In DivertTrafficToRomanaIPtablesChain() iptables rule created but activation failed ", rule.GetBody())
			return err2
		}

	}
	log.Info("DivertTrafficToRomanaIPtablesChain() successfully processed chain number", chain)
	return nil
}

// addIPtablesRule creates new iptable rule in database.
func (fw *IPtables) addIPtablesRule(rule *IPtablesRule) error {
	if err := fw.Store.addIPtablesRule(rule); err != nil {
		log.Error("In addIPtablesRule failed to add ", rule.GetBody())
		return err
	}

	return nil
}

// CreateRules creates iptables Rules for the given Romana chain
// to allow a traffic to flow between the Host and Endpoint.
func (fw *IPtables) CreateRules(chain int) error {
	log.Info("In CreateRules() for chain", chain)
	for _, rule := range fw.chains[chain].Rules {
		// First create rule record in database.
		err0 := fw.addIPtablesRule(rule)
		if err0 != nil {
			log.Error("In CreateRules() create db record for iptables rule ", rule.GetBody())
			return err0
		}

		err1 := fw.EnsureRule(rule, EnsureFirst)
		if err1 != nil {
			log.Error("In CreateRules() failed to create install firewall rule ", rule.GetBody())
			return err1
		}

		// Finally, set 'active' flag in database record.
		if err2 := fw.Store.switchIPtablesRule(rule, setRuleActive); err2 != nil {
			log.Error("In CreateRules() iptables rule created, but activation failed ", rule.GetBody())
			return err2
		}
	}
	log.Info("Creating firewall rules success")
	return nil
}

// CreateU32Rules creates wildcard iptables Rules for the given Romana chain.
// These Rules serve to restrict traffic between segments and tenants.
// * Deprecated, outdated *
func (fw *IPtables) CreateU32Rules(chain int) error {
	log.Info("Creating U32 firewall rules for chain", chain)
	chainName := fw.chains[chain].ChainName
	args := []string{"-w", "-A", chainName, "-m", "u32", "--u32", fw.u32filter, "-j", "ACCEPT"}
	_, err := fw.os.Exec(iptablesCmd, args)
	if err != nil {
		log.Error("Creating U32 firewall rules failed")
		return err
	}
	log.Info("Creating U32 firewall rules success")
	return nil
}

// CreateDefaultDropRule creates iptables Rules to drop all unidentified traffic
// in the given chain
func (fw *IPtables) CreateDefaultDropRule(chain int) error {
	return fw.CreateDefaultRule(chain, targetDrop)
}

// CreateDefaultRule creates iptables rule for a chain with the
// specified target
func (fw *IPtables) CreateDefaultRule(chain int, target string) error {
	log.Infof("In CreateDefaultRule() %s rules for chain %d", target, chain)
	chainName := fw.chains[chain].ChainName
	body := fmt.Sprintf("%s %s %s", chainName, "-j", target)
	rule := &IPtablesRule{
		Body:  body,
		State: setRuleInactive.String(),
	}

	// First create rule record in database.
	err0 := fw.addIPtablesRule(rule)
	if err0 != nil {
		log.Error("In CreateDefaultRule() create db record for iptables rule ", rule.GetBody())
		return err0
	}

	err1 := fw.EnsureRule(rule, EnsureLast)
	if err1 != nil {
		log.Errorf("In CreateDefaultRule() %s rules failed", target)
		return err1
	}

	// Finally, set 'active' flag in database record.
	if err2 := fw.Store.switchIPtablesRule(rule, setRuleActive); err2 != nil {
		log.Error("In CreateDefaultRule() iptables rule created but activation failed ", rule.GetBody())
		return err2
	}

	log.Info("In CreateDefaultRule() success")
	return nil
}

// prepareTenantSegmentMask returns integer representation of a bitmask
// for tenant+segment bits in pseudo network.
func (fw *IPtables) prepareTenantSegmentMask() uint64 {
	var res uint64
	tenantBits := fw.networkConfig.TenantBits()
	segmentBits := fw.networkConfig.SegmentBits()
	combinedTSBits := tenantBits + segmentBits
	endpointBits := fw.networkConfig.EndpointBits()
	res = ((1 << combinedTSBits) - 1) << endpointBits
	return res
}

// ipToInt transforms IP address from net.IP form to integer form.
// Taken from IPAM/config, should really be in some shared library.
func ipToInt(ip net.IP) uint64 {
	return uint64(ip[12])<<24 | uint64(ip[13])<<16 | uint64(ip[14])<<8 | uint64(ip[15])
}

// RomanaNetNetmaskInt returns integer representation of pseudo net netmask.
func (fw *IPtables) RomanaNetNetmaskInt() (uint64, error) {
	cidr, err := fw.networkConfig.PNetCIDR()
	if err != nil {
		return 0, err
	}
	pNetMaskInt, err := MaskToInt(cidr.Mask)
	if err != nil {
		return 0, err
	}
	return pNetMaskInt, nil
}

// MaskToInt converts net.IPMask to integer.
// TODO Not strictly firewall method, maybe put in different place.
func MaskToInt(mask net.IPMask) (uint64, error) {
	var imask uint64
	m, err := strconv.ParseInt(mask.String(), 16, 64)
	imask = uint64(m)
	if err != nil {
		return 0, err
	}
	return imask, nil
}

// prepareU32Rules generates IPtables Rules for U32 iptables module.
// This Rules implemet Romana tenant/segment filtering
//   Return the filter Rules for the iptables u32 module.
//   Goal: Filter out any traffic that does not have the same tenant and segment
//   bits in the destination address as the interface itself.
//   These bits can be extracted from the IP address: This is the address that
//   we are assigning to the interface. The function is to be called when the
//   interface is set up. The passed-in address therefore can be trusted: It is
//   not taken from a packet.
//      Example:
//      ipAddr = "10.0.1.4"
//
//      Return:
//      filter = '12&0xFF00FF00=0xA000100&&16&0xFF00FF00=0xA000100'
//      chainPrefix = 'ROMANA-T0S1-'
//
//   TODO Refactor chain-prefix routine into separate function (prepareChainPrefix).
//   Also return the chain-prefix we'll use for this interface. This is
//   typically a string such as:
//       ROMANA-T<tenant-id>S<segment-id>-
//   For example, with tenant 1 and segment 2, this would be:
//       ROMANA-T1S2-
func (fw *IPtables) prepareU32Rules(ipAddr net.IP) (string, string, error) {
	fullMask, err := fw.prepareNetmaskBits()
	if err != nil {
		return "", "", err
	}
	addr := ipToInt(ipAddr)
	if err != nil {
		return "", "", err
	}
	filter1 := fmt.Sprintf("0x%X=0x%X", fullMask, addr&fullMask)
	filter := fmt.Sprintf("12&%s&&16&%s", filter1, filter1)
	tenantID := fw.extractTenantID(addr)
	segmentID := fw.extractSegmentID(addr)
	chainPrefix := fmt.Sprintf("ROMANA-T%dS%d-", tenantID, segmentID)
	return filter, chainPrefix, nil
}

// prepareNetmaskBits returns integer representation of pseudo network bitmask.
// Used to prepare u32 firewall Rules that would match ip addresses belonging
// to given tenant/segment pair.
func (fw *IPtables) prepareNetmaskBits() (uint64, error) {
	iCidrMask, err := fw.RomanaNetNetmaskInt()
	if err != nil {
		return 0, err
	}
	combinedTSMask := fw.prepareTenantSegmentMask()
	res := iCidrMask | combinedTSMask
	return res, nil
}

// extractSegmentID extracts segment id from the given ip address.
// This is possible because segment id encoded in the ip address.
func (fw *IPtables) extractSegmentID(addr uint64) uint64 {
	endpointBits := fw.networkConfig.EndpointBits()
	segmentBits := fw.networkConfig.SegmentBits()
	sid := (addr >> endpointBits) & ((1 << segmentBits) - 1)
	return sid
}

// extractTenantID extracts tenant id from given the ip address.
// This is possible because tenant id encoded in the ip address.
func (fw *IPtables) extractTenantID(addr uint64) uint64 {
	endpointBits := fw.networkConfig.EndpointBits()
	segmentBits := fw.networkConfig.SegmentBits()
	tenantBits := fw.networkConfig.TenantBits()
	tid := (addr >> (endpointBits + segmentBits)) & ((1 << tenantBits) - 1)
	return tid
}

// ProvisionEndpoint creates iptables Rules for given endpoint in given environment
func (fw IPtables) ProvisionEndpoint() error {
	log.Info("In ProvisionEndpoint() with firewall.")

	if !fw.initialized {
		return fmt.Errorf("In ProvisionEndpoint(), can not provision uninitialized firewall, use Init()")
	}

	err := fw.CreateChains(fw.chains)
	if err != nil {
		return err
	}

	log.Info("Firewall: creating rules")
	for chain := range fw.chains {
		if err := fw.CreateRules(chain); err != nil {
			return err
		}
	}

	if err := fw.CreateDefaultDropRule(ForwardOutChainIndex); err != nil {
		return err
	}

	log.Info("Firewall: diverting traffic")
	for _, chain := range fw.chains {
		if err := fw.DivertTrafficToRomanaIPtablesChain(chain, installDivertRules); err != nil {
			return err
		}
	}

	return nil
}

// Cleanup implements Firewall interface.
func (fw IPtables) Cleanup(netif FirewallEndpoint) error {
	if err := fw.deleteIPtablesRulesBySubstring(netif.GetName()); err != nil {
		log.Errorf("In Cleanup() failed to clean firewall for %s", netif.GetName())
		return err
	}
	return nil
}

// deleteIPtablesRulesBySubstring uninstalls iptables Rules matching given
// substring and deletes them from database. Has no effect on 'inactive' Rules.
func (fw *IPtables) deleteIPtablesRulesBySubstring(substring string) error {
	if substring == "" {
		return common.NewError("Empty substring specified to deleteIPtablesRulesBySubstring")
	}
	rules, err := fw.Store.findIPtablesRules(substring)
	if err != nil {
		return err
	}

	for _, rule := range *rules {
		if rule.State == setRuleInactive.String() {
			continue
		}

		err = fw.deleteIPtablesRule(&rule)
		if err != nil {
			return err
		}
	}

	return nil
}

// deleteIPtablesRule attempts to uninstall and delete the given rule.
func (fw *IPtables) deleteIPtablesRule(rule *IPtablesRule) error {
	if err := fw.Store.switchIPtablesRule(rule, setRuleInactive); err != nil {
		log.Error("In deleteIPtablesRule() failed to deactivate the rule", rule.GetBody())
		return err
	}

	if err1 := fw.EnsureRule(rule, EnsureAbsent); err1 != nil {
		log.Errorf("In deleteIPtablesRule() rule %s set inactive but failed to uninstall", rule.GetBody())
		return err1
	}

	if err2 := fw.Store.deleteIPtablesRule(rule); err2 != nil {
		log.Errorf("In deleteIPtablesRule() rule %s set inactive and uninstalled but failed to delete DB record", rule.GetBody())
		return err2
	}
	return nil
}

// EnsureRule verifies if given iptables rule exists and creates if it's not.
func (fw IPtables) EnsureRule(rule FirewallRule, opType RuleState) error {
	ruleExists := fw.isRuleExist(rule)

	args := []string{}
	if ruleExists {

		switch opType {
		case EnsureAbsent:
			args = append(args, "-D")
		default:
			log.Info("In EnsureRule - nothing to do ", rule.GetBody())
			return nil
		}
	} else {

		switch opType {
		case EnsureLast:
			args = append(args, "-A")
		case EnsureFirst:
			args = append(args, "-I")
		default:
			log.Info("In EnsureRule - nothing to do ", rule.GetBody())
			return nil
		}
	}

	args = append(args, strings.Split(rule.GetBody(), " ")...)
	cmdStr := iptablesCmd + " " + strings.Join(args, " ")
	out, err := fw.os.Exec(iptablesCmd, args)
	if err != nil {
		log.Errorf("[%s]: %s failed for rule %s with error %v, saying [%s]", cmdStr, opType, rule.GetBody(), err, string(out))
	} else {
		if out != nil && len(out) > 0 {
			log.Infof("%s success %s: [%s]", opType, rule.GetBody(), string(out))
		} else {
			log.Infof("%s success %s", opType, rule.GetBody())
		}
	}

	return err
}

// ListRules implements Firewall interface
func (fw IPtables) ListRules() ([]IPtablesRule, error) {
	return fw.Store.listIPtablesRules()
}
