package netstack

import (
	"context"
	"fmt"
	"net"
	"reflect"
	"strconv"
	"strings"

	"github.com/alibaba/kubeskoop/pkg/skoop/model"
	"github.com/alibaba/kubeskoop/pkg/skoop/utils"

	"github.com/beevik/etree"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
)

type XTablesVerdict uint

const (
	XTablesVerdictAccept   XTablesVerdict = 0
	XTablesVerdictDrop     XTablesVerdict = 1
	XTablesVerdictReject   XTablesVerdict = 2
	XTablesVerdictReturn   XTablesVerdict = 3
	XTablesVerdictContinue XTablesVerdict = 4
)

type IPTablesRuleError struct {
	Rule    string
	Message string
}

func (e *IPTablesRuleError) Error() string {
	return fmt.Sprintf("rule: %q, message: %q", e.Rule, e.Message)
}

type ErrIptablesUnsupported struct {
	Message string
}

func (u ErrIptablesUnsupported) Error() string {
	return fmt.Sprintf("unsupported %s", u.Message)
}

type Target interface{}

type ExtensionTarget interface {
	Do(ctx context.Context, packet *model.Packet, iif, oif string) (XTablesVerdict, error)
}

type NopTarget struct{}
type AcceptTarget struct{}
type DropTarget struct{}
type MasqueradeTarget struct{}
type RejectTarget struct{}
type ReturnTarget struct{}
type CallTarget struct {
	Chain string
}
type GotoTarget struct {
	Chain string
}

type DNATTarget struct {
	ToDestination string `ipt:"--to-destination"`
	Random        bool   `ipt:"--random"`
	Persistent    bool   `ipt:"--persistent"`
}

type SNATTarget struct {
	ToSource    string `ipt:"--to-source"`
	Random      bool   `ipt:"--random"`
	RandomFully bool   `ipt:"--random-fully"`
	Persistent  bool   `ipt:"--persistent"`
}

func (s *SNATTarget) Do(_ context.Context, _ *model.Packet, _, _ string) (XTablesVerdict, error) {
	return XTablesVerdictAccept, nil
}

func (t *DNATTarget) Do(_ context.Context, _ *model.Packet, _, _ string) (XTablesVerdict, error) {
	return XTablesVerdictAccept, nil
}

type MarkTarget struct {
}

func (t *MarkTarget) Do(_ context.Context, _ *model.Packet, _, _ string) (XTablesVerdict, error) {
	return XTablesVerdictAccept, nil
}

type NoTrackTarget struct {
}

func (t *NoTrackTarget) Do(_ context.Context, _ *model.Packet, _, _ string) (XTablesVerdict, error) {
	return XTablesVerdictAccept, nil
}

type TPProxyTarget struct {
}

func (t *TPProxyTarget) Do(_ context.Context, _ *model.Packet, _, _ string) (XTablesVerdict, error) {
	return XTablesVerdictAccept, nil
}

type Matcher interface {
	Match(ctx context.Context, packet *model.Packet, iif, oif string) (bool, error)
}

type TCP struct {
	Option string
	Value  uint16
}

func (t *TCP) Match(_ context.Context, packet *model.Packet, _, _ string) (bool, error) {
	if packet.Protocol != model.TCP {
		return false, nil
	}
	switch t.Option {
	case "dport":
		return packet.Dport == t.Value, nil
	case "sport":
		return packet.Sport == t.Value, nil
	default:
		return false, ErrIptablesUnsupported{fmt.Sprintf("tcp match option %s", t.Option)}
	}
}

func (t *TCP) String() string {
	return fmt.Sprintf("--%s %d", t.Option, t.Value)
}

type IP struct {
	Option string
	Value  string
}

func (ip *IP) Match(_ context.Context, packet *model.Packet, iif, oif string) (bool, error) {
	switch ip.Option {
	case "i":
		return ip.Value == iif, nil
	case "o":
		return ip.Value == oif, nil
	case "s":
		return utils.IPMatchPrefix(packet.Src, ip.Value)
	case "d":
		return utils.IPMatchPrefix(packet.Dst, ip.Value)
	case "p":
		return strings.EqualFold(string(packet.Protocol), ip.Value), nil
	default:
		return false, ErrIptablesUnsupported{fmt.Sprintf("match option %s", ip.Option)}
	}
}

func (ip *IP) String() string {
	return fmt.Sprintf("-%s %s", ip.Option, ip.Value)
}

type UDP struct {
	Option string
	Value  uint16
}

func (udp *UDP) Match(_ context.Context, packet *model.Packet, _, _ string) (bool, error) {
	if packet.Protocol != model.UDP {
		return false, nil
	}
	switch udp.Option {
	case "dport":
		return packet.Dport == udp.Value, nil
	case "sport":
		return packet.Sport == udp.Value, nil
	default:

		return false, ErrIptablesUnsupported{fmt.Sprintf("udp match option %s", udp.Option)}
	}
}

func (udp *UDP) String() string {
	return fmt.Sprintf("--%s %d", udp.Option, udp.Value)
}

type Conntrack struct {
	Option string
	Value  string
}

func (conntrack *Conntrack) Match(_ context.Context, _ *model.Packet, _, _ string) (bool, error) {
	return true, nil
}
func (conntrack *Conntrack) String() string {
	return fmt.Sprintf("-m conntrack --%s %s", conntrack.Option, conntrack.Value)
}

type Set struct {
	Option string
	Value  string
}

func (set *Set) parseSetArgument() (string, string, error) {
	arr := strings.Fields(set.Value)
	if len(arr) != 2 {
		return "", "", fmt.Errorf("invalid set argument format %s", set.Value)
	}

	return arr[0], arr[1], nil
}

func (set *Set) Match(ctx context.Context, packet *model.Packet, _, _ string) (bool, error) {
	setName, flags, err := set.parseSetArgument()
	if err != nil {
		return false, err
	}
	ipsetManager, ok := ctx.Value(ContextIPSetKey).(*IPSetManager)
	if !ok || ipsetManager == nil {
		return false, fmt.Errorf("cannot get ipset from context")
	}

	ipset := ipsetManager.GetIPSet(setName)
	if ipset == nil {
		return false, nil
	}

	ip := func(field string) net.IP {
		switch field {
		case "src":
			return packet.Src
		case "dst":
			return packet.Dst
		default:
			return net.IPv4(0, 0, 0, 0)
		}
	}
	port := func(field string) uint16 {
		switch field {
		case "src":
			return packet.Sport
		case "dst":
			return packet.Dport
		default:
			return 0
		}
	}

	var key string

	switch ipset.Type {
	case "hash:net":
		addr := ip(flags)
		for m := range ipset.Members {
			if match, _ := utils.IPMatchPrefix(addr, m); match {
				return true, nil
			}
		}
		return false, nil
	case "hash:ip,port":
		fields := strings.Split(flags, ",")
		addr := ip(fields[0])
		port := port(fields[1])
		key = fmt.Sprintf("%s,%s:%d", addr, packet.Protocol, port)
	case "hash:ip,port,ip":
		fields := strings.Split(flags, ",")
		addr := ip(fields[0])
		port := port(fields[1])
		addr2 := ip(fields[2])
		key = fmt.Sprintf("%s,%s:%d,%s", addr, packet.Protocol, port, addr2)
	case "bitmap:port":
		port := port(flags)
		key = fmt.Sprintf("%d", port)
	default:
		return false, ErrIptablesUnsupported{fmt.Sprintf("ipset type %s of %s", ipset.Type, setName)}
	}
	_, ok = ipset.Members[key]
	return ok, nil
}

func (set *Set) String() string {
	return fmt.Sprintf("-m set --match-set %s %s", set.Option, set.Value)
}

type Comment struct {
	Option string
	Value  string
}

func (c *Comment) Match(_ context.Context, _ *model.Packet, _, _ string) (bool, error) {
	return true, nil
}

func (c *Comment) String() string {
	return fmt.Sprintf("-m comment --%s %s", c.Option, c.Value)
}

type MultiPort struct {
	Option string
	Value  string
}

func (mp *MultiPort) matchPort(port uint16) (bool, error) {
	fields := strings.Split(mp.Value, ",")
	for _, f := range fields {
		if strings.Contains(f, ":") {
			portRange := strings.Split(f, ":")
			first, _ := strconv.Atoi(portRange[0])
			last, _ := strconv.Atoi(portRange[1])
			if port >= uint16(first) && port <= uint16(last) {
				return true, nil
			}
		} else {
			p, _ := strconv.Atoi(f)
			if port == uint16(p) {
				return true, nil
			}
		}
	}
	return false, nil
}

func (mp *MultiPort) Match(_ context.Context, packet *model.Packet, _, _ string) (bool, error) {
	switch mp.Option {
	case "dports":
		return mp.matchPort(packet.Dport)
	case "sports":
		return mp.matchPort(packet.Sport)
	case "ports":
		ok, err := mp.matchPort(packet.Sport)
		if err != nil {
			return false, err
		}
		if ok {
			return true, nil
		}
		ok, err = mp.matchPort(packet.Dport)
		if err != nil {
			return false, err
		}
		return ok, nil
	default:
		return false, ErrIptablesUnsupported{fmt.Sprintf("multiport option %s", mp.Option)}
	}
}

func (mp *MultiPort) String() string {
	return fmt.Sprintf("-m multiport --%s %s", mp.Option, mp.Value)
}

func parseMarkValue(s string) (uint32, uint32) {
	if strings.Contains(s, "/") {
		fields := strings.Split(s, "/")
		val, _ := strconv.ParseInt(fields[0], 0, 32)
		mask, _ := strconv.ParseInt(fields[1], 0, 32)
		return uint32(val), uint32(mask)
	}
	val, _ := strconv.ParseInt(s, 0, 32)
	return uint32(val), 0xffffffff
}

type Mark struct {
	Option string
	Value  string
}

func (m *Mark) Match(_ context.Context, packet *model.Packet, _, _ string) (bool, error) {
	mark, mask := parseMarkValue(m.Value)
	return mark == packet.Mark&mask, nil
}

func (m *Mark) String() string {
	return fmt.Sprintf("-m mark --mark %s", m.Value)
}

const (
	RtnUnspec      = 0x0
	RtnUnicast     = 0x1
	RtnLocal       = 0x2
	RtnBroadcast   = 0x3
	RtnAnycast     = 0x4
	RtnMulticast   = 0x5
	RtnBlackhole   = 0x6
	RtnUnreachable = 0x7
	RtnProhibit    = 0x8
	RtnThrow       = 0x9
	RtnNat         = 0xa
	RtnXresolve    = 0xb
)

type AddrType struct {
	Option string
	Value  string
}

func (t *AddrType) Match(ctx context.Context, packet *model.Packet, iif, oif string) (bool, error) {
	var addr net.IP
	switch t.Option {
	case "src-type":
		addr = packet.Src
	case "dst-type":
		addr = packet.Dst
	case "limit-iface-in":
		return iif == t.Value, nil
	case "limit-iface-out":
		return oif == t.Value, nil
	}

	switch t.Value {
	case "UNSPEC":
		return addr.IsUnspecified(), nil
	case "MULTICAST":
		return addr.IsMulticast(), nil
	}

	router, ok := ctx.Value(ContextRouterKey).(Router)
	if !ok {
		return false, fmt.Errorf("cannot get router from context, router: %#+v", router)
	}

	var addrType int

	route, err := router.TableRoute(RtTableLocal, packet)
	if err != nil {
		if err == ErrNoRouteToHost {
			addrType = RtnUnicast
		} else {
			return false, err
		}
	} else {
		addrType = route.Type
	}

	switch t.Value {
	case "UNICAST":
		return addrType == RtnUnicast, nil
	case "LOCAL":
		return addrType == RtnLocal, nil
	case "BROADCAST":
		return addrType == RtnBroadcast, nil
	case "ANYCAST":
		return addrType == RtnAnycast, nil
	case "MULTICAST":
		return addrType == RtnMulticast, nil
	case "BLACKHOLE":
		return addrType == RtnBlackhole, nil
	case "UNREACHABLE":
		return addrType == RtnUnreachable, nil
	case "PROHIBIT":
		return addrType == RtnProhibit, nil
	}
	return false, nil
}

func (t *AddrType) String() string {
	return fmt.Sprintf("-m addrtype --%s %s", t.Option, t.Value)
}

type Statistic struct {
	Option string
	Value  string
}

func (s *Statistic) Match(_ context.Context, _ *model.Packet, _, _ string) (bool, error) {
	return true, nil
}

func (s *Statistic) String() string {
	return fmt.Sprintf("-m statistic %s %s", s.Option, s.Value)
}

type Physdev struct {
	Option string
	Value  string
}

func (physdev *Physdev) Match(_ context.Context, _ *model.Packet, _, _ string) (bool, error) {
	//FIXME 这里有问题
	return true, nil
}

func (physdev *Physdev) String() string {
	return fmt.Sprintf("-m physdev %s %s", physdev.Option, physdev.Value)
}

type Socket struct {
	Option string
	Value  string
}

func (s *Socket) Socket(_ context.Context, _ *model.Packet, _, _ string) (bool, error) {
	//FIXME 这里有问题
	return true, nil
}

func (s *Socket) String() string {
	return fmt.Sprintf("-m socket %s %s", s.Option, s.Value)
}

type RPFilter struct {
	Option string
	Value  string
}

func (rp *RPFilter) Match(ctx context.Context, packet *model.Packet, iif, oif string) (bool, error) {
	if rp.Option == "loose" {
		return true, nil
	}

	router, ok := ctx.Value(ContextRouterKey).(Router)
	if !ok {
		return false, fmt.Errorf("cannot get router from context, router: %#+v", router)
	}
	route, err := router.Route(packet, iif, oif)
	if err != nil {
		if err == ErrNoRouteToHost {
			return false, nil
		}
		return false, err
	}

	match := route.OifName == iif
	if rp.Option == "invert" {
		match = !match
	}
	return match, nil
}

func (rp *RPFilter) String() string {
	return fmt.Sprintf("-m rpfilter %s %s", rp.Option, rp.Value)
}

type match struct {
	matcher Matcher
	invert  bool
}

func (m *match) match(ctx context.Context, packet *model.Packet, iif, oif string) (bool, error) {
	v, err := m.matcher.Match(ctx, packet, iif, oif)
	if err != nil {
		return v, err
	}

	if m.invert {
		return !v, nil
	}

	return v, nil
}

type rule struct {
	matches []*match
	target  interface{}
}

func (r *rule) match(ctx context.Context, packet *model.Packet, iif, oif string) (bool, error) {
	for _, m := range r.matches {
		v, err := m.match(ctx, packet, iif, oif)
		if err != nil {
			return false, err
		}
		if !v {
			return false, nil
		}
	}
	return true, nil
}

func (r *rule) String() string {
	var matches []string
	for _, m := range r.matches {
		matches = append(matches, fmt.Sprintf("%v", m))
	}
	return fmt.Sprintf("%s -j %v", strings.Join(matches, " "), r.target)
}

type iptChain struct {
	name   string
	policy Verdict
	rules  []*rule
}

type xTable struct {
	name   string
	chains map[string]*iptChain
}

func (xt *xTable) tracePacket(ctx context.Context, hook NFHook, packet *model.Packet, iif, oif string) (verdict Verdict, trace Trace, err error) {
	type stackFrame struct {
		chain *iptChain
		pos   int
	}

	chain := xt.chains[hook.String()]
	stack := utils.NewStack[*stackFrame](&stackFrame{
		chain: chain,
		pos:   0,
	})

	verdict = VerdictDrop

	var frame *stackFrame

	buildTrace := func(frame *stackFrame) {
		if frame == nil {
			return
		}

		if frame.pos >= len(frame.chain.rules) {
			return
		}

		trace = append(trace, fmt.Sprintf("%s %s %s", xt.name, chain.name, frame.chain.rules[frame.pos]))
	}

	defer func() {
		if verdict != VerdictAccept {
			buildTrace(frame)
			for !stack.Empty() {
				buildTrace(stack.Pop())
			}
		}
	}()

	for !stack.Empty() {
		frame = stack.Pop()
	chain:
		for pos := frame.pos; pos < len(frame.chain.rules); {
			rule := frame.chain.rules[pos]
			pos++

			v, err := rule.match(ctx, packet, iif, oif)
			if err != nil {
				return VerdictDrop, trace, &IPTablesRuleError{Rule: rule.String(), Message: err.Error()}
			}

			if v {
				trace = append(trace, fmt.Sprintf("%s %s %s", xt.name, chain.name, rule))
				switch target := rule.target.(type) {
				case *AcceptTarget:
					return VerdictAccept, trace, nil
				case *DropTarget, RejectTarget:
					return VerdictDrop, trace, nil
				case *NopTarget:
					continue
				case *ReturnTarget:
					break chain
				case *CallTarget:
					stack.Push(&stackFrame{chain, pos})
					targetChain := xt.chains[target.Chain]
					stack.Push(&stackFrame{targetChain, 0})
					break chain
				case *GotoTarget:
					targetChain := xt.chains[target.Chain]
					stack.Push(&stackFrame{targetChain, 0})
					break chain
				case ExtensionTarget:
					verdict, err := target.Do(ctx, packet, iif, oif)
					if err != nil {
						return VerdictDrop, trace, err
					}
					switch verdict {
					case XTablesVerdictAccept:
						return VerdictAccept, trace, nil
					case XTablesVerdictDrop, XTablesVerdictReject:
						return VerdictDrop, trace, nil
					case XTablesVerdictContinue:
						continue
					case XTablesVerdictReturn:
						break chain
					default:
						panic(fmt.Sprintf("unknown verdict %v", verdict))
					}
				default:
					return VerdictDrop, trace, &IPTablesRuleError{Rule: rule.String(), Message: fmt.Sprintf("unsupported target (%T)%v", target, target)}
				}
			}
		}
	}
	return chain.policy, trace, nil
}

type IPTables interface {
	TracePacket(ctx context.Context, hook NFHook, table string, packet *model.Packet, iif, oif string) (Verdict, Trace, error)
	Empty() error
	DefaultAccept() error
}

func createEmptyTable(name string, chains ...string) *xTable {
	iptChains := make(map[string]*iptChain)
	for _, n := range chains {
		iptChains[n] = &iptChain{name: n, policy: VerdictAccept}
	}

	return &xTable{
		name:   name,
		chains: iptChains,
	}
}

type emptyIPTables struct {
	xTables map[string]*xTable
}

func (ipt *emptyIPTables) TracePacket(_ context.Context, _ NFHook, _ string, _ *model.Packet, _, _ string) (Verdict, Trace, error) {
	return VerdictAccept, nil, nil
}

func (ipt *emptyIPTables) Empty() error {
	return nil
}

func (ipt *emptyIPTables) DefaultAccept() error {
	return nil
}

func createEmptyIPTables() *emptyIPTables {
	return &emptyIPTables{
		xTables: map[string]*xTable{
			"raw":    createEmptyTable("raw", "PREROUTING", "OUTPUT"),
			"mangle": createEmptyTable("mangle", "PREROUTING", "INPUT", "FORWARD", "OUTPUT", "POSTROUTING"),
			"nat":    createEmptyTable("nat", "PREROUTING", "INPUT", "OUTPUT", "POSTROUTING"),
			"filter": createEmptyTable("filter", "INPUT", "FORWARD", "OUTPUT"),
		},
	}
}

type defaultIPTables struct {
	xTables map[string]*xTable
}

type Trace []string

func (t Trace) String() string {
	var indent []string
	for _, s := range t {
		indent = append(indent, fmt.Sprintf("    %s", s))
	}
	return strings.Join(indent, "\n")
}

func (ipt *defaultIPTables) TracePacket(ctx context.Context, hook NFHook, table string, packet *model.Packet, iif, oif string) (Verdict, Trace, error) {
	var trace Trace
	xtable := ipt.xTables[table]
	if xtable == nil {
		return VerdictAccept, trace, nil
	}
	verdict, trace, err := xtable.tracePacket(ctx, hook, packet, iif, oif)
	return verdict, trace, err
}

func (ipt *defaultIPTables) Empty() error {
	for _, tbl := range ipt.xTables {
		for _, chain := range tbl.chains {
			if len(chain.rules) > 0 {
				return fmt.Errorf("table %s chain %s is not empty", tbl.name, chain.name)
			}
		}
	}
	return nil
}

func (ipt *defaultIPTables) DefaultAccept() error {
	for _, tbl := range ipt.xTables {
		for _, chain := range tbl.chains {
			if chain.policy != VerdictAccept {
				return fmt.Errorf("table %s chain %s default policy is not ACCEPT", tbl.name, chain.name)
			}
		}
	}
	return nil
}

var ModuleTypes = map[string]reflect.Type{
	"tcp":       reflect.TypeOf(TCP{}),
	"udp":       reflect.TypeOf(UDP{}),
	"match":     reflect.TypeOf(IP{}),
	"set":       reflect.TypeOf(Set{}),
	"comment":   reflect.TypeOf(Comment{}),
	"multiport": reflect.TypeOf(MultiPort{}),
	"mark":      reflect.TypeOf(Mark{}),
	"statistic": reflect.TypeOf(Statistic{}),
	"conntrack": reflect.TypeOf(Conntrack{}),
	"addrtype":  reflect.TypeOf(AddrType{}),
	"rpfilter":  reflect.TypeOf(RPFilter{}),
}

var ActionTypes = map[string]reflect.Type{
	"DNAT":       reflect.TypeOf(DNATTarget{}),
	"SNAT":       reflect.TypeOf(SNATTarget{}),
	"MASQUERADE": reflect.TypeOf(MasqueradeTarget{}),
	"MARK":       reflect.TypeOf(MarkTarget{}),
	"ACCEPT":     reflect.TypeOf(AcceptTarget{}),
	"DROP":       reflect.TypeOf(DropTarget{}),
	"RETURN":     reflect.TypeOf(ReturnTarget{}),
	"REJECT":     reflect.TypeOf(RejectTarget{}),
	"NOTRACK":    reflect.TypeOf(NoTrackTarget{}),
	"TPROXY":     reflect.TypeOf(TPProxyTarget{}),
}

func setField(s reflect.Value, fieldName string, value string) error {
	t := s.Type()
	if t.Kind() != reflect.Struct {
		return fmt.Errorf("%s is not a struct", s)
	}
	_, ok := t.FieldByName(fieldName)
	if !ok {
		return fmt.Errorf("%s has no field %s", s, fieldName)
	}

	field := s.FieldByName(fieldName)

	switch field.Type().Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		i, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("error parse %s to int", value))
		}
		field.SetInt(i)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		i, err := strconv.ParseUint(value, 10, 64)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("error parse %s to uint", value))
		}
		field.SetUint(i)
	case reflect.Float32:
		f, err := strconv.ParseFloat(value, 32)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("error parse %s to float32", value))
		}
		field.SetFloat(f)
	case reflect.Float64:
		f, err := strconv.ParseFloat(value, 64)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("error parse %s to float64", value))
		}
		field.SetFloat(f)
	case reflect.String:
		field.SetString(value)
	default:
		return fmt.Errorf("unsupported field type %s", field.Type().Kind().String())
	}
	return nil
}

func createMatcher(moduleType reflect.Type, matchField string, value string) (Matcher, error) {
	module := reflect.New(moduleType)
	if err := setField(module.Elem(), "Option", matchField); err != nil {
		return nil, err
	}
	if err := setField(module.Elem(), "Value", value); err != nil {
		return nil, err
	}
	return module.Interface().(Matcher), nil
}

func createTarget(actionType reflect.Type, params map[string]string) (Target, error) {
	action := reflect.New(actionType)
	for i := 0; i < actionType.NumField(); i++ {
		field := actionType.Field(i)
		tag, ok := field.Tag.Lookup("ipt")
		if !ok {
			continue
		}
		val, ok := params[tag]
		if !ok {
			continue
		}
		if err := setField(action.Elem(), field.Name, val); err != nil {
			return nil, err
		}
	}
	return action.Interface(), nil
}

func parseOneRule(xmlRule *etree.Element) (*rule, error) {
	var conditions []*match
	xmlConditions := xmlRule.FindElement("conditions")
	if xmlConditions != nil {
		for _, xmlModule := range xmlConditions.ChildElements() {
			moduleKey := xmlModule.Tag
			moduleType, ok := ModuleTypes[moduleKey]
			if !ok {
				return nil, fmt.Errorf("unspported match module %s", moduleKey)
			}
			for _, xmlExpr := range xmlModule.ChildElements() {
				key := xmlExpr.Tag
				value := xmlExpr.Text()
				invertAttr := xmlExpr.SelectAttr("invert")
				invert := invertAttr != nil && invertAttr.Value == "true"
				matcher, err := createMatcher(moduleType, key, value)
				if err != nil {
					panic(err)
				}
				conditions = append(conditions, &match{matcher: matcher, invert: invert})
			}
		}
	}

	var target Target
	xmlAction := xmlRule.FindElement("actions")
	if xmlAction == nil || len(xmlAction.ChildElements()) == 0 {
		target = &NopTarget{}
		return &rule{matches: conditions, target: target}, nil
	}

	action := xmlAction.ChildElements()[0]

	if action.Tag == "call" {
		target = &CallTarget{Chain: action.ChildElements()[0].Tag}
	} else if action.Tag == "goto" {
		target = &CallTarget{Chain: action.ChildElements()[0].Tag}
	} else {
		actionType, ok := ActionTypes[action.Tag]
		if !ok {
			return nil, fmt.Errorf("unsupported action %s", action.Tag)
		}
		params := make(map[string]string)
		for _, child := range action.ChildElements() {
			params[child.Tag] = child.Text()
		}
		var err error
		target, err = createTarget(actionType, params)
		if err != nil {
			return nil, errors.Wrapf(err, "error create target %s, err: %v", action.Tag, err)
		}
	}

	return &rule{matches: conditions, target: target}, nil
}
func parseOneTable(xmlTable *etree.Element) (*xTable, error) {
	tableName := xmlTable.SelectAttr("name").Value
	chains := make(map[string]*iptChain)
	for _, xmlChain := range xmlTable.ChildElements() {
		policy := VerdictAccept
		name := xmlChain.SelectAttr("name").Value
		policyAttr := xmlChain.SelectAttr("policy")
		if policyAttr != nil && policyAttr.Value == "DROP" {
			policy = VerdictDrop
		}
		var rules []*rule
		for _, xmlRule := range xmlChain.ChildElements() {
			rule, err := parseOneRule(xmlRule)
			if err != nil {
				return nil, err
			}
			rules = append(rules, rule)
		}
		chains[name] = &iptChain{
			name:   name,
			policy: policy,
			rules:  rules,
		}

	}
	return &xTable{name: tableName, chains: chains}, nil
}

func ParseIPTables(dump string) (ipt IPTables) {
	defer func() {
		if r := recover(); r != nil {
			log.Errorf("error parse iptables, err: %v", r)
			ipt = createEmptyIPTables()
		}
	}()
	if dump == "" {
		return createEmptyIPTables()
	}
	doc := etree.NewDocument()
	err := doc.ReadFromString(dump)
	if err != nil {
		panic(err)
	}

	tables := make(map[string]*xTable)
	for _, xmlTable := range doc.Root().ChildElements() {
		table, err := parseOneTable(xmlTable)
		if err != nil {
			panic(err)
		}
		tables[table.name] = table
	}
	return &defaultIPTables{xTables: tables}
}
