<|start_filename|>iptables/func_rules.go<|end_filename|>
package iptables

import (
	"bytes"
	"fmt"
	"sort"

	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func ruleHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["from_port"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["to_port"].(string)))
	p := protocolForValue(m["protocol"].(string))
	buf.WriteString(fmt.Sprintf("%s-", p))
	buf.WriteString(fmt.Sprintf("%s-", m["iface_out"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["iface_in"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["state"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["icmptype"].(string)))
	buf.WriteString(fmt.Sprintf("%t-", m["fragment"].(bool)))
	buf.WriteString(fmt.Sprintf("%s-", m["action"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["position"].(string)))

	if v, ok := m["cidr_blocks"]; ok {
		vs := v.(*schema.Set).List()
		s := make([]string, len(vs))
		for i, raw := range vs {
			s[i] = raw.(string)
		}
		sort.Strings(s)
		for _, v := range s {
			buf.WriteString(fmt.Sprintf("%s-", v))
		}
	}

	return hashcodeString(buf.String())
}

func expandCIDRInGressList(gress []interface{}, version string) []interface{} {
	var newGress []interface{}

	for _, raw := range gress {
		ma := raw.(map[string]interface{})
		lengthCIDRBlocks := len(ma["cidr_blocks"].(*schema.Set).List())

		if lengthCIDRBlocks == 0 {
			newCIDR := make(map[string]interface{})
			newCIDR["from_port"] = ma["from_port"].(string)
			newCIDR["to_port"] = ma["to_port"].(string)
			newCIDR["protocol"] = ma["protocol"].(string)
			newCIDR["iface_in"] = ma["iface_in"].(string)
			newCIDR["iface_out"] = ma["iface_out"].(string)
			newCIDR["action"] = ma["action"].(string)
			newCIDR["state"] = ma["state"].(string)
			newCIDR["icmptype"] = ma["icmptype"].(string)
			newCIDR["fragment"] = ma["fragment"].(bool)
			newCIDR["position"] = ma["position"].(string)
			switch version {
			case ipv4ver:
				newCIDR["cidr_blocks"] = ipv4All
			case ipv6ver:
				newCIDR["cidr_blocks"] = ipv6All
			}
			newGress = append(newGress, newCIDR)
		} else {
			for _, cidr := range ma["cidr_blocks"].(*schema.Set).List() {
				newCIDR := make(map[string]interface{})
				newCIDR["from_port"] = ma["from_port"].(string)
				newCIDR["to_port"] = ma["to_port"].(string)
				newCIDR["protocol"] = ma["protocol"].(string)
				newCIDR["iface_in"] = ma["iface_in"].(string)
				newCIDR["iface_out"] = ma["iface_out"].(string)
				newCIDR["action"] = ma["action"].(string)
				newCIDR["state"] = ma["state"].(string)
				newCIDR["icmptype"] = ma["icmptype"].(string)
				newCIDR["fragment"] = ma["fragment"].(bool)
				newCIDR["position"] = ma["position"].(string)
				newCIDR["cidr_blocks"] = cidr.(string)
				newGress = append(newGress, newCIDR)
			}
		}
	}

	return newGress
}

func expandCIDRInGress(gress interface{}, version string) []interface{} {
	var returnGress []interface{}
	ma := gress.(map[string]interface{})
	lengthCIDRBlocks := len(ma["cidr_blocks"].(*schema.Set).List())
	if lengthCIDRBlocks == 0 {
		newCIDR := make(map[string]interface{})
		newCIDR["from_port"] = ma["from_port"].(string)
		newCIDR["to_port"] = ma["to_port"].(string)
		newCIDR["protocol"] = ma["protocol"].(string)
		newCIDR["iface_in"] = ma["iface_in"].(string)
		newCIDR["iface_out"] = ma["iface_out"].(string)
		newCIDR["action"] = ma["action"].(string)
		newCIDR["state"] = ma["state"].(string)
		newCIDR["icmptype"] = ma["icmptype"].(string)
		newCIDR["fragment"] = ma["fragment"].(bool)
		newCIDR["position"] = ma["position"].(string)
		switch version {
		case ipv4ver:
			newCIDR["cidr_blocks"] = ipv4All
		case ipv6ver:
			newCIDR["cidr_blocks"] = ipv6All
		}
		returnGress = append(returnGress, newCIDR)
	} else {
		for _, cidr := range ma["cidr_blocks"].(*schema.Set).List() {
			newCIDR := make(map[string]interface{})
			newCIDR["from_port"] = ma["from_port"].(string)
			newCIDR["to_port"] = ma["to_port"].(string)
			newCIDR["protocol"] = ma["protocol"].(string)
			newCIDR["iface_in"] = ma["iface_in"].(string)
			newCIDR["iface_out"] = ma["iface_out"].(string)
			newCIDR["action"] = ma["action"].(string)
			newCIDR["state"] = ma["state"].(string)
			newCIDR["icmptype"] = ma["icmptype"].(string)
			newCIDR["fragment"] = ma["fragment"].(bool)
			newCIDR["position"] = ma["position"].(string)
			newCIDR["cidr_blocks"] = cidr.(string)
			returnGress = append(returnGress, newCIDR)
		}
	}

	return returnGress
}

func checkRulesPositionAndCIDRList(d *schema.ResourceData) error {
	lenONCIDR := len(d.Get("on_cidr_blocks").(*schema.Set).List())
	for _, ingress := range d.Get("ingress").(*schema.Set).List() {
		ingressMap := ingress.(map[string]interface{})
		if ingressMap["position"].(string) != "?" {
			if lenONCIDR > one {
				return fmt.Errorf("position not possible with multiple 'on_cidr_blocks'")
			}
			if len(ingressMap["cidr_blocks"].(*schema.Set).List()) > one {
				return fmt.Errorf("position not possible with multiple 'cidr_blocks'")
			}
		}
	}
	for _, egress := range d.Get("egress").(*schema.Set).List() {
		egressMap := egress.(map[string]interface{})
		if egressMap["position"].(string) != "?" {
			if lenONCIDR > one {
				return fmt.Errorf("position not possible with multiple 'on_cidr_blocks'")
			}
			if len(egressMap["cidr_blocks"].(*schema.Set).List()) > one {
				return fmt.Errorf("position not possible with multiple 'cidr_blocks'")
			}
		}
	}

	return nil
}


<|start_filename|>iptables/resource_nat.go<|end_filename|>
package iptables

import (
	"bytes"
	"context"
	"fmt"
	"sort"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
	strDnat string = "dnat"
	strSnat string = "snat"
)

func resourceNat() *schema.Resource {
	return &schema.Resource{
		CreateContext: resourceNatCreate,
		ReadContext:   resourceNatRead,
		UpdateContext: resourceNatUpdate,
		DeleteContext: resourceNatDelete,
		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
			},
			"on_cidr_blocks": {
				Type:     schema.TypeSet,
				Required: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
			"snat": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"to_port": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "0",
						},
						"protocol": {
							Type:      schema.TypeString,
							Optional:  true,
							StateFunc: protocolStateFunc,
							Default:   "all",
						},
						"iface": {
							Type:      schema.TypeString,
							Required:  true,
							StateFunc: ifaceStateFunc,
						},
						"filter_cidr_blocks": {
							Type:     schema.TypeSet,
							Optional: true,
							Elem:     &schema.Schema{Type: schema.TypeString},
						},
						"except_cidr_blocks": {
							Type:             schema.TypeString,
							Optional:         true,
							ValidateDiagFunc: validateCIDRNetworkOrHostV4(),
						},
						"nat_ip": {
							Type:     schema.TypeString,
							Required: true,
						},
						"nth_every": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "",
						},
						"nth_packet": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "0",
						},
						"position": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "?",
						},
					},
				},
				Set: natHash,
			},
			"dnat": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"to_port": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "0",
						},
						"protocol": {
							Type:      schema.TypeString,
							Optional:  true,
							StateFunc: protocolStateFunc,
							Default:   "all",
						},
						"iface": {
							Type:      schema.TypeString,
							Required:  true,
							StateFunc: ifaceStateFunc,
						},
						"filter_cidr_blocks": {
							Type:     schema.TypeSet,
							Optional: true,
							Elem:     &schema.Schema{Type: schema.TypeString},
						},
						"except_cidr_blocks": {
							Type:             schema.TypeString,
							Optional:         true,
							ValidateDiagFunc: validateCIDRNetworkOrHostV4(),
						},
						"nat_ip": {
							Type:     schema.TypeString,
							Required: true,
						},
						"nth_every": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "",
						},
						"nth_packet": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "0",
						},
						"position": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "?",
						},
					},
				},
				Set: natHash,
			},
		},
	}
}

func resourceNatCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	if diags := resourceNatUpdate(ctx, d, m); len(diags) > 0 {
		return diags
	}
	d.SetId(d.Get("name").(string) + "!")

	return nil
}

func resourceNatRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	if d.HasChange("on_cidr_blocks") {
		oldOnCIDR, _ := d.GetChange("on_cidr_blocks")
		err := natReadOnCIDR(ctx, oldOnCIDR.(*schema.Set).List(), d, m)
		if err != nil {
			return diag.FromErr(err)
		}
	} else {
		err := natReadOnCIDR(ctx, d.Get("on_cidr_blocks").(*schema.Set).List(), d, m)
		if err != nil {
			return diag.FromErr(err)
		}
	}
	if (len(d.Get("snat").(*schema.Set).List()) == 0) && (len(d.Get("dnat").(*schema.Set).List()) == 0) {
		d.SetId("")
	}

	return nil
}

func resourceNatUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	if d.HasChange("name") {
		o, n := d.GetChange("name")
		if o != "" {
			d.SetId(n.(string) + "!")
		}
	}

	err := checkNatPositionAndCIDRList(d)
	if err != nil {
		d.SetId("")

		return diag.FromErr(err)
	}
	if d.HasChange("on_cidr_blocks") {
		oldOnCIDR, newOnCIDR := d.GetChange("on_cidr_blocks")
		onCIDRRemove := computeRemove(oldOnCIDR.(*schema.Set).List(), newOnCIDR.(*schema.Set).List())
		err = natRemoveOnCIDR(ctx, onCIDRRemove, d, m)
		if err != nil {
			d.SetId("")

			return diag.FromErr(err)
		}
		err = natAddOnCIDR(ctx, d.Get("on_cidr_blocks").(*schema.Set).List(), d, m)
		if err != nil {
			d.SetId("")

			return diag.FromErr(err)
		}
	} else {
		err = natAddOnCIDR(ctx, d.Get("on_cidr_blocks").(*schema.Set).List(), d, m)
		if err != nil {
			d.SetId("")

			return diag.FromErr(err)
		}
	}
	client := m.(*Client)
	err = client.saveV4(ctx)
	if err != nil {
		return diag.FromErr(fmt.Errorf("iptables save failed : %s", err))
	}

	return nil
}

func resourceNatDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	err := natRemoveOnCIDR(ctx, d.Get("on_cidr_blocks").(*schema.Set).List(), d, m)
	if err != nil {
		d.SetId(d.Get("name").(string) + "!")

		return diag.FromErr(err)
	}
	client := m.(*Client)
	err = client.saveV4(ctx)
	if err != nil {
		return diag.FromErr(fmt.Errorf("iptables save failed : %s", err))
	}

	return nil
}

func natHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["to_port"].(string)))
	p := protocolForValue(m["protocol"].(string))
	buf.WriteString(fmt.Sprintf("%s-", p))
	buf.WriteString(fmt.Sprintf("%s-", strings.ReplaceAll(m["nat_ip"].(string), "/32", "")))
	buf.WriteString(fmt.Sprintf("%s-", m["iface"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["position"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["nth_every"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["nth_packet"].(string)))

	if v, ok := m["filter_cidr_blocks"]; ok {
		vs := v.(*schema.Set).List()
		s := make([]string, len(vs))
		for i, raw := range vs {
			s[i] = raw.(string)
		}
		sort.Strings(s)
		for _, v := range s {
			buf.WriteString(fmt.Sprintf("%s-", v))
		}
	}

	return hashcodeString(buf.String())
}

func natReadOnCIDR(ctx context.Context, onCIDRList []interface{}, d *schema.ResourceData, m interface{}) error {
	for _, cidr := range onCIDRList {
		if d.HasChange("snat") {
			oldSnat, _ := d.GetChange("snat")
			err := natListCommand(ctx, cidr.(string), oldSnat.(*schema.Set).List(), strSnat, httpGet, d, m, false)
			if err != nil {
				return err
			}
		} else {
			snat := d.Get("snat")
			err := natListCommand(ctx, cidr.(string), snat.(*schema.Set).List(), strSnat, httpGet, d, m, false)
			if err != nil {
				return err
			}
		}
		if d.HasChange("dnat") {
			oldDnat, _ := d.GetChange("dnat")
			err := natListCommand(ctx, cidr.(string), oldDnat.(*schema.Set).List(), strDnat, httpGet, d, m, false)
			if err != nil {
				return err
			}
		} else {
			dnat := d.Get("dnat")
			err := natListCommand(ctx, cidr.(string), dnat.(*schema.Set).List(), strDnat, httpGet, d, m, false)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func natRemoveOnCIDR(ctx context.Context, onCIDRList []interface{}, d *schema.ResourceData, m interface{}) error {
	for _, cidr := range onCIDRList {
		if d.HasChange("snat") {
			oldSnat, _ := d.GetChange("snat")
			err := natListCommand(ctx, cidr.(string), oldSnat.(*schema.Set).List(), strSnat, httpDel, d, m, false)
			if err != nil {
				return err
			}
		} else {
			snat := d.Get("snat")
			err := natListCommand(ctx, cidr.(string), snat.(*schema.Set).List(), strSnat, httpDel, d, m, false)
			if err != nil {
				return err
			}
		}
		if d.HasChange("dnat") {
			oldDnat, _ := d.GetChange("dnat")
			err := natListCommand(ctx, cidr.(string), oldDnat.(*schema.Set).List(), strDnat, httpDel, d, m, false)
			if err != nil {
				return err
			}
		} else {
			dnat := d.Get("dnat")
			err := natListCommand(ctx, cidr.(string), dnat.(*schema.Set).List(), strDnat, httpDel, d, m, false)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func natAddOnCIDR(ctx context.Context, onCIDRList []interface{}, d *schema.ResourceData, m interface{}) error {
	for _, cidr := range onCIDRList {
		err := checkCIDRBlocksString(cidr.(string), ipv4ver)
		if err != nil {
			return err
		}
		if d.HasChange("snat") {
			oldSnat, newSnat := d.GetChange("snat")
			oldSnatSetDiff := oldSnat.(*schema.Set).Difference(newSnat.(*schema.Set))
			newSnatSetDiff := newSnat.(*schema.Set).Difference(oldSnat.(*schema.Set))

			oldSnatSetDiffExpanded := expandCIDRInNatList(oldSnatSetDiff.List(), strSnat, ipv4ver)
			newSnatSetDiffExpanded := expandCIDRInNatList(newSnatSetDiff.List(), strSnat, ipv4ver)
			oldSnatSetExpandedRemove := computeOutSlicesOfMap(oldSnatSetDiffExpanded, newSnatSetDiffExpanded)

			err := checkNat(newSnat.(*schema.Set).List())
			if err != nil {
				return err
			}
			err = natListCommand(ctx, cidr.(string), oldSnatSetExpandedRemove, strSnat, httpDel, d, m, true)
			if err != nil {
				return err
			}
			err = natListCommand(ctx, cidr.(string), newSnat.(*schema.Set).List(), strSnat, httpPut, d, m, false)
			if err != nil {
				return err
			}
		} else {
			err := checkNat(d.Get("snat").(*schema.Set).List())
			if err != nil {
				return err
			}
			err = natListCommand(ctx, cidr.(string), d.Get("snat").(*schema.Set).List(), strSnat, httpPut, d, m, false)
			if err != nil {
				return err
			}
		}
		if d.HasChange("dnat") {
			oldDnat, newDnat := d.GetChange("dnat")
			oldDnatSetDiff := oldDnat.(*schema.Set).Difference(newDnat.(*schema.Set))
			newDnatSetDiff := newDnat.(*schema.Set).Difference(oldDnat.(*schema.Set))

			oldDnatSetDiffExpand := expandCIDRInNatList(oldDnatSetDiff.List(), strDnat, ipv4ver)
			newDnatSetDiffExpand := expandCIDRInNatList(newDnatSetDiff.List(), strDnat, ipv4ver)
			oldDnatSetExpandedRemove := computeOutSlicesOfMap(oldDnatSetDiffExpand, newDnatSetDiffExpand)

			err := checkNat(newDnat.(*schema.Set).List())
			if err != nil {
				return err
			}
			err = natListCommand(ctx, cidr.(string), oldDnatSetExpandedRemove, strDnat, httpDel, d, m, true)
			if err != nil {
				return err
			}
			err = natListCommand(ctx, cidr.(string), newDnat.(*schema.Set).List(), strDnat, httpPut, d, m, false)
			if err != nil {
				return err
			}
		} else {
			err := checkNat(d.Get("dnat").(*schema.Set).List())
			if err != nil {
				return err
			}
			err = natListCommand(ctx, cidr.(string), d.Get("dnat").(*schema.Set).List(), strDnat, httpPut, d, m, false)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func natListCommand(ctx context.Context, onCIDR string, natList []interface{}, way string, method string,
	d *schema.ResourceData, m interface{}, cidrExpanded bool) error {
	switch method {
	case httpGet:
		if cidrExpanded {
			return fmt.Errorf("internal error : natListCommand Get with cidrExpanded")
		}
		var saves []map[string]interface{}
		for _, natElement := range natList {
			natOK := true
			natOKnoPos := false
			natExpanded := expandCIDRInNat(natElement, way, ipv4ver)
			for _, natExpandedElement := range natExpanded {
				err := natCmd(ctx, onCIDR, natExpandedElement, httpGet, m)
				if err != nil {
					if !strings.Contains(err.Error(), noExists) {
						return err
					}
					natOK = false
					if err.Error() == noExistsNoPosErr {
						natOKnoPos = true
					}
				}
			}
			if natOK {
				saves = append(saves, natElement.(map[string]interface{}))
			}
			if natOKnoPos {
				natElementNew := natElement.(map[string]interface{})
				natElementNew["position"] = "?"
				saves = append(saves, natElementNew)
			}
		}
		switch way {
		case strSnat:
			tfErr := d.Set("snat", saves)
			if tfErr != nil {
				panic(tfErr)
			}
		case strDnat:
			tfErr := d.Set("dnat", saves)
			if tfErr != nil {
				panic(tfErr)
			}
		}

		return nil
	case httpDel:
		if cidrExpanded {
			for _, natElement := range natList {
				err := natCmd(ctx, onCIDR, natElement, httpDel, m)
				if err != nil {
					return err
				}
			}
		} else {
			for _, natElement := range natList {
				natExpanded := expandCIDRInNat(natElement, way, ipv4ver)
				for _, natExpandedElement := range natExpanded {
					err := natCmd(ctx, onCIDR, natExpandedElement, httpDel, m)
					if err != nil {
						return err
					}
				}
			}
		}

		return nil
	case httpPut:
		if cidrExpanded {
			for _, natElement := range natList {
				err := checkCIDRBlocksInMap(natElement.(map[string]interface{}), ipv4ver)
				if err != nil {
					return err
				}
				err = natCmd(ctx, onCIDR, natElement, httpPut, m)
				if err != nil {
					return err
				}
			}
		} else {
			for _, natElement := range natList {
				natExpand := expandCIDRInNat(natElement, way, ipv4ver)
				for _, natExpandElement := range natExpand {
					err := checkCIDRBlocksInMap(natExpandElement.(map[string]interface{}), ipv4ver)
					if err != nil {
						return err
					}
					err = natCmd(ctx, onCIDR, natExpandElement, httpPut, m)
					if err != nil {
						return err
					}
				}
			}
		}

		return nil
	}

	return fmt.Errorf("internal error : unknown method for natListCommand")
}

func natCmd(ctx context.Context, onCIDR string, nat interface{}, method string, m interface{}) error {
	client := m.(*Client)

	ma := nat.(map[string]interface{})
	var dstOk string
	var srcOk string
	var natRule Rule
	var natRuleNoPos Rule
	if (ma["to_port"].(string) != "0") && (ma["protocol"].(string) == strAll) {
		return fmt.Errorf("need protocol for to_port specification")
	}
	switch ma["action"].(string) {
	case strSnat:
		maskOk := strings.Contains(onCIDR, "/")
		if maskOk {
			srcOk = onCIDR
		} else {
			srcOk = strings.Join([]string{onCIDR, "/32"}, "")
		}
		maskOk = strings.Contains(ma["cidr_blocks"].(string), "/")
		if maskOk {
			dstOk = ma["cidr_blocks"].(string)
		} else {
			dstOk = strings.Join([]string{ma["cidr_blocks"].(string), "/32"}, "")
		}
		natRule = Rule{
			Action:    ma["action"].(string),
			Chain:     "POSTROUTING",
			Proto:     ma["protocol"].(string),
			Iface:     ma["iface"].(string),
			IPSrc:     strings.ReplaceAll(srcOk, "/", "_"),
			IPDst:     strings.ReplaceAll(dstOk, "/", "_"),
			Dports:    ma["to_port"].(string),
			IPNat:     strings.ReplaceAll(ma["nat_ip"].(string), "/32", ""),
			NthEvery:  ma["nth_every"].(string),
			NthPacket: ma["nth_packet"].(string),
			Position:  ma["position"].(string),
			Except:    ma["except"].(bool),
		}
		natRuleNoPos = Rule{
			Action:    ma["action"].(string),
			Chain:     "POSTROUTING",
			Proto:     ma["protocol"].(string),
			Iface:     ma["iface"].(string),
			IPSrc:     strings.ReplaceAll(srcOk, "/", "_"),
			IPDst:     strings.ReplaceAll(dstOk, "/", "_"),
			Dports:    ma["to_port"].(string),
			IPNat:     strings.ReplaceAll(ma["nat_ip"].(string), "/32", ""),
			NthEvery:  ma["nth_every"].(string),
			NthPacket: ma["nth_packet"].(string),
			Position:  "?",
			Except:    ma["except"].(bool),
		}
	case strDnat:
		maskOk := strings.Contains(onCIDR, "/")
		if maskOk {
			dstOk = onCIDR
		} else {
			dstOk = strings.Join([]string{onCIDR, "/32"}, "")
		}
		maskOk = strings.Contains(ma["cidr_blocks"].(string), "/")
		if maskOk {
			srcOk = ma["cidr_blocks"].(string)
		} else {
			srcOk = strings.Join([]string{ma["cidr_blocks"].(string), "/32"}, "")
		}
		natRule = Rule{
			Action:    ma["action"].(string),
			Chain:     "PREROUTING",
			Proto:     ma["protocol"].(string),
			Iface:     ma["iface"].(string),
			IPSrc:     strings.ReplaceAll(srcOk, "/", "_"),
			IPDst:     strings.ReplaceAll(dstOk, "/", "_"),
			Dports:    ma["to_port"].(string),
			IPNat:     strings.ReplaceAll(ma["nat_ip"].(string), "/32", ""),
			NthEvery:  ma["nth_every"].(string),
			NthPacket: ma["nth_packet"].(string),
			Position:  ma["position"].(string),
			Except:    ma["except"].(bool),
		}
		natRuleNoPos = Rule{
			Action:    ma["action"].(string),
			Chain:     "PREROUTING",
			Proto:     ma["protocol"].(string),
			Iface:     ma["iface"].(string),
			IPSrc:     strings.ReplaceAll(srcOk, "/", "_"),
			IPDst:     strings.ReplaceAll(dstOk, "/", "_"),
			Dports:    ma["to_port"].(string),
			IPNat:     strings.ReplaceAll(ma["nat_ip"].(string), "/32", ""),
			NthEvery:  ma["nth_every"].(string),
			NthPacket: ma["nth_packet"].(string),
			Position:  "?",
			Except:    ma["except"].(bool),
		}
	}

	switch method {
	case httpDel:
		natExistsNoPos, err := client.natAPIV4(ctx, natRuleNoPos, httpGet)
		if err != nil {
			return fmt.Errorf("check rules nat for %s %v failed : %s", onCIDR, natRuleNoPos, err)
		}
		if natExistsNoPos {
			ret, err := client.natAPIV4(ctx, natRuleNoPos, httpDel)
			if !ret || err != nil {
				return fmt.Errorf("delete rules nat %s %v failed : %s", onCIDR, natRuleNoPos, err)
			}
		}
	case httpPut:
		natExists, err := client.natAPIV4(ctx, natRule, httpGet)
		if err != nil {
			return fmt.Errorf("check rules nat for %s %v failed : %s", onCIDR, natRule, err)
		}
		if !natExists {
			if ma["position"].(string) != "?" {
				natExistsNoPos, err := client.natAPIV4(ctx, natRuleNoPos, httpGet)
				if err != nil {
					return fmt.Errorf("check rules nat for %s %v failed : %s", onCIDR, natRuleNoPos, err)
				}
				if natExistsNoPos {
					ret, err := client.natAPIV4(ctx, natRuleNoPos, httpDel)
					if !ret || err != nil {
						return fmt.Errorf("delete rules with bad position on nat %s %v failed : %s", onCIDR, natRuleNoPos, err)
					}
					ret, err = client.natAPIV4(ctx, natRule, httpPut)
					if !ret || err != nil {
						return fmt.Errorf("add rules nat %s %v failed : %s", onCIDR, natRule, err)
					}
				} else {
					ret, err := client.natAPIV4(ctx, natRule, httpPut)
					if !ret || err != nil {
						return fmt.Errorf("add rules nat %s %v failed : %s", onCIDR, natRule, err)
					}
				}
			} else {
				ret, err := client.natAPIV4(ctx, natRule, httpPut)
				if !ret || err != nil {
					return fmt.Errorf("add rules nat %s %v failed : %s", onCIDR, natRule, err)
				}
			}
		}
	case httpGet:
		natExists, err := client.natAPIV4(ctx, natRule, httpGet)
		if err != nil {
			return fmt.Errorf("check rules nat for %s %v failed : %s", onCIDR, natRule, err)
		}
		if !natExists {
			natExistsNoPos, err := client.natAPIV4(ctx, natRuleNoPos, httpGet)
			if err != nil {
				return fmt.Errorf("check rules nat for %s %v failed : %s", onCIDR, natRuleNoPos, err)
			}
			if natExistsNoPos {
				return fmt.Errorf(noExistsNoPosErr)
			}

			return fmt.Errorf(noExists)
		}
	}

	return nil
}


<|start_filename|>iptables/resource_project_ipv6.go<|end_filename|>
package iptables

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceProjectIPv6() *schema.Resource {
	return &schema.Resource{
		CreateContext: resourceProjectIPv6Create,
		ReadContext:   resourceProjectIPv6Read,
		UpdateContext: resourceProjectIPv6Update,
		DeleteContext: resourceProjectIPv6Delete,
		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validation.StringLenBetween(1, maxLengthProjectName),
			},
			"cidr_blocks": {
				Type:     schema.TypeSet,
				Required: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
			"position": {
				Type:         schema.TypeInt,
				Optional:     true,
				ForceNew:     true,
				Default:      0,
				ValidateFunc: validation.IntAtLeast(0),
			},
		},
	}
}

func resourceProjectIPv6Create(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	client := m.(*Client)

	if !client.IPv6 {
		return diag.FromErr(fmt.Errorf("ipv6 not enable on provider"))
	}
	checkExists, err := client.chainAPIV6(ctx, d.Get("name").(string), httpGet)
	if err != nil {
		return diag.FromErr(fmt.Errorf("check if project %s exist failed : %s", d.Get("name"), err))
	}
	if !checkExists {
		create, err := client.chainAPIV6(ctx, d.Get("name").(string), httpPut)
		if !create || err != nil {
			return diag.FromErr(fmt.Errorf("create project %s failed : %s", d.Get("name"), err))
		}
	} else {
		return diag.FromErr(fmt.Errorf("project %s already exist", d.Get("name")))
	}
	d.SetId(d.Get("name").(string) + "!")

	return resourceProjectIPv6Update(ctx, d, m)
}

func resourceProjectIPv6Read(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	client := m.(*Client)

	if !client.IPv6 {
		return diag.FromErr(fmt.Errorf("ipv6 not enable on provider"))
	}

	checkExists, err := client.chainAPIV6(ctx, d.Get("name").(string), httpGet)
	if err != nil {
		return diag.FromErr(fmt.Errorf("read project %s failed : %s", d.Get("name"), err))
	}
	if !checkExists {
		d.SetId("")

		return nil
	}
	if d.Get("position").(int) != 0 {
		routerChainName := strings.Join([]string{"router_chain_pos", strconv.Itoa(absolute(d.Get("position").(int)))}, "")
		checkExists, err := client.chainAPIV6(ctx, routerChainName, httpGet)
		if err != nil {
			return diag.FromErr(fmt.Errorf("read chain router_chain_pos %s failed : %s", routerChainName, err))
		}
		if !checkExists {
			tfErr := d.Set("position", 0)
			if tfErr != nil {
				panic(tfErr)
			}
		}
		routerChainPos, err := insertPosrouterV6(ctx, absolute(d.Get("position").(int)), httpGet, m)
		if err != nil {
			return diag.FromErr(fmt.Errorf("read position %d in router_chain failed : %s", d.Get("position").(int), err))
		}
		if !routerChainPos {
			tfErr := d.Set("position", absolute(d.Get("position").(int))*-1)
			if tfErr != nil {
				panic(tfErr)
			}
		} else {
			tfErr := d.Set("position", absolute(d.Get("position").(int)))
			if tfErr != nil {
				panic(tfErr)
			}
		}
	}

	var listCIDRSet []interface{}
	for _, cidr := range d.Get("cidr_blocks").(*schema.Set).List() {
		status, err := cidrForProjectV6(ctx, cidr.(string), absolute(d.Get("position").(int)), httpGet, d, m)
		if err == nil && status {
			listCIDRSet = append(listCIDRSet, cidr.(string))
		}
	}
	tfErr := d.Set("cidr_blocks", listCIDRSet)
	if tfErr != nil {
		panic(tfErr)
	}
	d.SetId(d.Get("name").(string) + "!")

	return nil
}

func resourceProjectIPv6Update(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	client := m.(*Client)

	if !client.IPv6 {
		return diag.FromErr(fmt.Errorf("ipv6 not enable on provider"))
	}
	positionChange := false
	var oPos, nPos interface{}
	oPos = 0
	if d.HasChange("position") {
		positionChange = true
		oPos, nPos = d.GetChange("position")
		if oPos.(int) != 0 {
			for _, cidr := range d.Get("cidr_blocks").(*schema.Set).List() {
				err := checkCIDRBlocksString(cidr.(string), ipv6ver)
				if err != nil {
					tfErr := d.Set("position", oPos.(int))
					if tfErr != nil {
						panic(tfErr)
					}

					return diag.FromErr(err)
				}
				_, err = cidrForProjectV6(ctx, cidr.(string), 0, httpPut, d, m)
				if err != nil {
					tfErr := d.Set("position", oPos.(int))
					if tfErr != nil {
						panic(tfErr)
					}

					return diag.FromErr(err)
				}
			}
			rulePosDel, err := insertPosrouterV6(ctx, absolute(oPos.(int)), httpDel, m)
			if !rulePosDel || err != nil {
				tfErr := d.Set("position", oPos.(int))
				if tfErr != nil {
					panic(tfErr)
				}

				return diag.FromErr(fmt.Errorf("delete rule for position %d failed : %s", oPos.(int), err))
			}
			routerChainName := strings.Join([]string{"router_chain_pos", strconv.Itoa(absolute(oPos.(int)))}, "")
			routeChainDel, err := client.chainAPIV6(ctx, routerChainName, httpDel)
			if !routeChainDel || err != nil {
				tfErr := d.Set("position", oPos.(int))
				if tfErr != nil {
					panic(tfErr)
				}

				return diag.FromErr(fmt.Errorf("delete chain %s failed : %s", routerChainName, err))
			}
			tfErr := d.Set("position", 0)
			if tfErr != nil {
				panic(tfErr)
			}
		}
		if nPos.(int) > 0 {
			routerChainName := strings.Join([]string{"router_chain_pos", strconv.Itoa(nPos.(int))}, "")
			checkExists, err := client.chainAPIV6(ctx, routerChainName, httpGet)
			if err != nil {
				tfErr := d.Set("position", 0)
				if tfErr != nil {
					panic(tfErr)
				}

				return diag.FromErr(fmt.Errorf("check if chain %s exist failed : %s", routerChainName, err))
			}
			if checkExists {
				tfErr := d.Set("position", 0)
				if tfErr != nil {
					panic(tfErr)
				}

				return diag.FromErr(fmt.Errorf("position %d already used", nPos.(int)))
			}
			create, err := client.chainAPIV6(ctx, routerChainName, httpPut)
			if !create || err != nil {
				tfErr := d.Set("position", 0)
				if tfErr != nil {
					panic(tfErr)
				}

				return diag.FromErr(fmt.Errorf("create chain %s for position : %s", routerChainName, err))
			}
			createPos, err := insertPosrouterV6(ctx, nPos.(int), httpPut, m)
			if !createPos || err != nil {
				removeChainPos, err2 := client.chainAPIV6(ctx, routerChainName, httpDel)
				if !removeChainPos || err2 != nil {
					tfErr := d.Set("position", 0)
					if tfErr != nil {
						panic(tfErr)
					}

					return diag.FromErr(fmt.Errorf("insert position in router_chain failed %s and "+
						"error for delete router_chain_pos %s (please delete manually) : %s",
						err, routerChainName, err2))
				}
				tfErr := d.Set("position", 0)
				if tfErr != nil {
					panic(tfErr)
				}

				return diag.FromErr(fmt.Errorf("insert position in router_chain failed : %s", err))
			}
			if !d.HasChange("cidr_blocks") {
				for _, cidr := range d.Get("cidr_blocks").(*schema.Set).List() {
					err := checkCIDRBlocksString(cidr.(string), ipv6ver)
					if err != nil {
						return diag.FromErr(err)
					}
					_, err = cidrForProjectV6(ctx, cidr.(string), nPos.(int), httpPut, d, m)
					if err != nil {
						return diag.FromErr(err)
					}
				}

				err := client.saveV6(ctx)
				if err != nil {
					return diag.FromErr(fmt.Errorf("ip6tables save failed : %s", err))
				}
			}
		}
	} else {
		nPos = d.Get("position")
	}
	if d.HasChange("cidr_blocks") {
		oldCIDR, newCIDR := d.GetChange("cidr_blocks")
		cidrListRemove := computeRemove(oldCIDR.(*schema.Set).List(), newCIDR.(*schema.Set).List())
		for _, cidr := range cidrListRemove {
			_, err := cidrForProjectV6(ctx, cidr.(string), nPos.(int), httpDel, d, m)
			if err != nil {
				return diag.FromErr(err)
			}
		}
		for _, cidr := range d.Get("cidr_blocks").(*schema.Set).List() {
			err := checkCIDRBlocksString(cidr.(string), ipv6ver)
			if err != nil {
				return diag.FromErr(err)
			}
			_, err = cidrForProjectV6(ctx, cidr.(string), nPos.(int), httpPut, d, m)
			if err != nil {
				return diag.FromErr(err)
			}
		}

		err := client.saveV6(ctx)
		if err != nil {
			return diag.FromErr(fmt.Errorf("ip6tables save failed : %s", err))
		}
	}
	if positionChange {
		if d.HasChange("cidr_blocks") && oPos.(int) == 0 && nPos.(int) > 0 {
			oldCIDR, _ := d.GetChange("cidr_blocks")
			for _, cidr := range oldCIDR.(*schema.Set).List() {
				_, err := cidrForProjectV6(ctx, cidr.(string), 0, httpDel, d, m)
				if err != nil {
					return diag.FromErr(err)
				}
			}
		}
		if nPos.(int) > 0 {
			for _, cidr := range d.Get("cidr_blocks").(*schema.Set).List() {
				_, err := cidrForProjectV6(ctx, cidr.(string), 0, httpDel, d, m)
				if err != nil {
					return diag.FromErr(err)
				}
			}
		}
	}
	tfErr := d.Set("position", nPos.(int))
	if tfErr != nil {
		panic(tfErr)
	}

	return nil
}

func resourceProjectIPv6Delete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	client := m.(*Client)

	if !client.IPv6 {
		return diag.FromErr(fmt.Errorf("ipv6 not enable on provider"))
	}

	cidrListRemove := d.Get("cidr_blocks").(*schema.Set).List()
	for _, cidr := range cidrListRemove {
		_, err := cidrForProjectV6(ctx, cidr.(string), absolute(d.Get("position").(int)), httpDel, d, m)
		if err != nil {
			return diag.FromErr(err)
		}
	}
	chainDeleted, err := client.chainAPIV6(ctx, d.Get("name").(string), httpDel)
	if !chainDeleted || err != nil {
		return diag.FromErr(fmt.Errorf("delete project %s failed : %s", d.Get("name"), err))
	}
	if d.Get("position").(int) != 0 {
		rulePosDel, err := insertPosrouterV6(ctx, absolute(d.Get("position").(int)), httpDel, m)
		if !rulePosDel || err != nil {
			return diag.FromErr(fmt.Errorf("delete rule for position %d failed : %s", d.Get("position").(int), err))
		}
		routerChainName := strings.Join([]string{"router_chain_pos", strconv.Itoa(absolute(d.Get("position").(int)))}, "")
		routeChainDel, err := client.chainAPIV6(ctx, routerChainName, httpDel)
		if !routeChainDel || err != nil {
			return diag.FromErr(fmt.Errorf("delete chain %s failed : %s", routerChainName, err))
		}
	}
	d.SetId("")
	err = client.saveV6(ctx)
	if err != nil {
		return diag.FromErr(fmt.Errorf("ip6tables save failed : %s", err))
	}

	return nil
}

func cidrForProjectV6(ctx context.Context,
	cidr string, position int, method string, d *schema.ResourceData, m interface{}) (bool, error) {
	routerChain := "router_chain"
	if position != 0 {
		routerChain = strings.Join([]string{"router_chain_pos", strconv.Itoa(absolute(d.Get("position").(int)))}, "")
	}

	// Route for source cidr
	client := m.(*Client)
	route := Rule{
		Action:   d.Get("name").(string),
		Chain:    routerChain,
		Proto:    "all",
		IfaceIn:  "*",
		IfaceOut: "*",
		IPSrc:    strings.ReplaceAll(cidr, "/", "_"),
		IPDst:    "::_0",
		Sports:   "0",
		Dports:   "0",
	}

	// Apply on table filter route for source cidr

	routeexists, err := client.rulesAPIV6(ctx, route, httpGet)
	if err != nil {
		return routeexists, fmt.Errorf("check rules for cidr %s failed : %s", cidr, err)
	}
	if !routeexists && method == httpPut {
		routeCIDR, err := client.rulesAPIV6(ctx, route, httpPut)
		if !routeCIDR || err != nil {
			return routeexists, fmt.Errorf("create rules source for cidr %s failed : %s", cidr, err)
		}
	}
	if routeexists && method == httpDel {
		routeCIDR, err := client.rulesAPIV6(ctx, route, httpDel)
		if !routeCIDR || err != nil {
			return routeexists, fmt.Errorf("delete rules source for cidr %s failed : %s", cidr, err)
		}
	}
	if routeexists && method == httpGet {
		return routeexists, nil
	}

	// Route for destination cidr
	route = Rule{
		Action:   d.Get("name").(string),
		Chain:    routerChain,
		Proto:    "all",
		IfaceIn:  "*",
		IfaceOut: "*",
		IPSrc:    "::_0",
		IPDst:    strings.ReplaceAll(cidr, "/", "_"),
		Sports:   "0",
		Dports:   "0",
	}
	// Apply on table filter route for destination cidr
	routeexists, err = client.rulesAPIV6(ctx, route, httpGet)
	if err != nil {
		return routeexists, fmt.Errorf("check rules for cidr %s failed : %s", cidr, err)
	}
	if !routeexists && method == httpPut {
		routeCIDR, err := client.rulesAPIV6(ctx, route, httpPut)
		if !routeCIDR || err != nil {
			return routeexists, fmt.Errorf("create rules destination for cidr %s failed : %s", cidr, err)
		}
	}
	if routeexists && method == httpDel {
		routeCIDR, err := client.rulesAPIV6(ctx, route, httpDel)
		if !routeCIDR || err != nil {
			return routeexists, fmt.Errorf("delete rules destination for cidr %s failed : %s", cidr, err)
		}
	}
	if !routeexists && method == httpGet {
		return routeexists, nil
	}

	return true, nil
}

func insertPosrouterV6(ctx context.Context, position int, method string, m interface{}) (bool, error) {
	client := m.(*Client)
	routerChainName := strings.Join([]string{"router_chain_pos", strconv.Itoa(position)}, "")
	route := Rule{
		Action:   routerChainName,
		Chain:    "router_chain",
		Proto:    "all",
		IfaceIn:  "*",
		IfaceOut: "*",
		IPSrc:    "::_0",
		IPDst:    "::_0",
		Sports:   "0",
		Dports:   "0",
		Position: strconv.Itoa(position),
	}
	routeNoPos := Rule{
		Action:   routerChainName,
		Chain:    "router_chain",
		Proto:    "all",
		IfaceIn:  "*",
		IfaceOut: "*",
		IPSrc:    "::_0",
		IPDst:    "::_0",
		Sports:   "0",
		Dports:   "0",
	}
	routeexists, err := client.rulesAPIV6(ctx, route, httpGet)
	if err != nil {
		return routeexists, fmt.Errorf("check rules for project position %d failed : %s", position, err)
	}
	if !routeexists && method == httpPut {
		routePut, err := client.rulesAPIV6(ctx, route, httpPut)
		if !routePut || err != nil {
			return routeexists, fmt.Errorf("create rules for project position %d failed : %s", position, err)
		}
	}
	if method == httpDel {
		if routeexists {
			routeDel, err := client.rulesAPIV6(ctx, route, httpDel)
			if !routeDel || err != nil {
				return routeexists, fmt.Errorf("delete rules for project position %d failed : %s", position, err)
			}
		} else {
			routeexistsNoPos, err := client.rulesAPIV4(ctx, routeNoPos, httpGet)
			if err != nil {
				return routeexistsNoPos, fmt.Errorf("check rules for project position "+
					"with bad position %d failed: %s", position, err)
			}
			if routeexistsNoPos {
				routeDel, err := client.rulesAPIV4(ctx, routeNoPos, httpDel)
				if !routeDel || err != nil {
					return routeexistsNoPos, fmt.Errorf("delete rules for project position "+
						"with bad position %d failed : %s", position, err)
				}
			}
		}
	}
	if !routeexists && method == httpGet {
		return routeexists, nil
	}

	return true, nil
}


<|start_filename|>iptables/client.go<|end_filename|>
package iptables

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
)

// Client = provider configuration.
type Client struct {
	HTTPS      bool
	Insecure   bool
	IPv6       bool
	Port       int
	FirewallIP string
	Logname    string
	Login      string
	Password   string
	AllowedIPs []interface{}
}

// Rule struc for generate iptables line.
type Rule struct {
	Except    bool
	Fragment  bool
	Notrack   bool
	Action    string
	State     string
	Icmptype  string
	Chain     string
	Proto     string
	IfaceIn   string
	IfaceOut  string
	Iface     string
	IPSrc     string
	IPDst     string
	IPNat     string
	Sports    string
	Dports    string
	Tcpflags1 string
	Tcpflags2 string
	Position  string
	NthEvery  string
	NthPacket string
	Logprefix string
	Tcpmss    string
}

// NewClient configure.
func NewClient(ctx context.Context, c *Config, login string, password string) (*Client, error) {
	client := &Client{
		FirewallIP: c.firewallIP,
		Port:       c.firewallPortAPI,
		AllowedIPs: c.allowedIPs,
		HTTPS:      c.https,
		Insecure:   c.insecure,
		Logname:    c.logname,
		Login:      login,
		Password:   password,
		IPv6:       c.ipv6Enable,
	}

	// Allow no default rules
	if os.Getenv("CONFIG_IPTABLES_TERRAFORM_NODEFAULT") == "" {
		checkExistsRouter, err := client.chainAPIV4(ctx, "router_chain", "GET")
		if err != nil {
			return nil, err
		}
		if !checkExistsRouter {
			createChain, err := client.chainAPIV4(ctx, "router_chain", "PUT")
			if !createChain || err != nil {
				return nil, fmt.Errorf("create chain router failed : %s", err)
			}
		}
		//	Add AllowedIPs on TCP Firewal_IP:Port
		for _, cidr := range client.AllowedIPs {
			// raw notrack on Firewal_IP:Port
			acceptAPI := Rule{
				Action:    "CT",
				Chain:     "PREROUTING",
				Proto:     "tcp",
				IfaceIn:   "*",
				IfaceOut:  "*",
				IPSrc:     strings.ReplaceAll(cidr.(string), "/", "_"),
				IPDst:     client.FirewallIP,
				Sports:    "0",
				Dports:    strconv.Itoa(client.Port),
				Tcpflags1: "SYN,RST,ACK,FIN",
				Tcpflags2: "SYN",
				Notrack:   true,
				Position:  "?",
			}
			routeexists, err := client.rawAPIV4(ctx, acceptAPI, "GET")
			if err != nil {
				return nil, fmt.Errorf("check rules (raw) allowed IP for API for cidr %s failed : %s", cidr.(string), err)
			}
			if !routeexists {
				routeCIDR, err := client.rawAPIV4(ctx, acceptAPI, "PUT")
				if !routeCIDR || err != nil {
					return nil, fmt.Errorf("create rules (raw) allowed IP for API for cidr %s failed : %s", cidr.(string), err)
				}
			}

			// ingress on Firewal_IP:Port
			acceptAPI = Rule{
				Action:   "ACCEPT",
				Chain:    "router_chain",
				Proto:    "tcp",
				IfaceIn:  "*",
				IfaceOut: "*",
				IPSrc:    strings.ReplaceAll(cidr.(string), "/", "_"),
				IPDst:    client.FirewallIP,
				Sports:   "0",
				Dports:   strconv.Itoa(client.Port),
				Position: "?",
			}
			routeexists, err = client.rulesAPIV4(ctx, acceptAPI, "GET")
			if err != nil {
				return nil, fmt.Errorf("check rules (ingress) allowed IP for API for cidr %s failed : %s", cidr.(string), err)
			}
			if !routeexists {
				routeCIDR, err := client.rulesAPIV4(ctx, acceptAPI, "PUT")
				if !routeCIDR || err != nil {
					return nil, fmt.Errorf("create rules (ingress) allowed IP for API for cidr %s failed : %s", cidr.(string), err)
				}
			}

			// egress on Firewal_IP:Port
			acceptAPI = Rule{
				Action:   "ACCEPT",
				Chain:    "router_chain",
				Proto:    "tcp",
				IfaceIn:  "*",
				IfaceOut: "*",
				IPSrc:    client.FirewallIP,
				IPDst:    strings.ReplaceAll(cidr.(string), "/", "_"),
				Sports:   strconv.Itoa(client.Port),
				Dports:   "0",
				Position: "?",
			}
			routeexists, err = client.rulesAPIV4(ctx, acceptAPI, "GET")
			if err != nil {
				return nil, fmt.Errorf("check rules (egress) allowed IP for API for cidr %s failed : %s", cidr.(string), err)
			}
			if !routeexists {
				routeCIDR, err := client.rulesAPIV4(ctx, acceptAPI, "PUT")
				if !routeCIDR || err != nil {
					return nil, fmt.Errorf("create rules (egress) allowed IP for API for cidr %s failed : %s", cidr.(string), err)
				}
			}
		}

		// Add rules for default chain
		defaultTable := []string{"INPUT", "FORWARD", "OUTPUT"}
		for _, table := range defaultTable {
			routeDefault := Rule{
				Action:   "router_chain",
				Chain:    table,
				Proto:    "all",
				IfaceIn:  "*",
				IfaceOut: "*",
				IPSrc:    "0.0.0.0_0",
				IPDst:    "0.0.0.0_0",
				Sports:   "0",
				Dports:   "0",
				Position: "?",
			}
			ruleexists, err := client.rulesAPIV4(ctx, routeDefault, "GET")
			if err != nil {
				return nil, fmt.Errorf("check default rules %s failed : %s", table, err)
			}
			if !ruleexists {
				resp, err := client.rulesAPIV4(ctx, routeDefault, "PUT")
				if !resp || err != nil {
					return nil, fmt.Errorf("create default rules %s failed : %s", table, err)
				}
			}
			if !c.noAddDefaultDrop {
				ruleDrop := Rule{
					Action:   "DROP",
					Chain:    table,
					Proto:    "all",
					IfaceIn:  "*",
					IfaceOut: "*",
					IPSrc:    "0.0.0.0_0",
					IPDst:    "0.0.0.0_0",
					Sports:   "0",
					Dports:   "0",
					Position: "?",
				}
				ruleexists, err = client.rulesAPIV4(ctx, ruleDrop, "GET")
				if err != nil {
					return nil, fmt.Errorf("check default rules drop %s failed : %s", table, err)
				}
				if !ruleexists {
					resp, err := client.rulesAPIV4(ctx, ruleDrop, "PUT")
					if !resp || err != nil {
						return nil, fmt.Errorf("create default rules drop %s failed : %s", table, err)
					}
				}
			}
		}
		if c.ipv6Enable {
			checkExistsRouter, err := client.chainAPIV6(ctx, "router_chain", "GET")
			if err != nil {
				return nil, err
			}
			if !checkExistsRouter {
				createChain, err := client.chainAPIV6(ctx, "router_chain", "PUT")
				if !createChain || err != nil {
					return nil, fmt.Errorf("create chain router v6 failed : %s", err)
				}
			}
			for _, table := range defaultTable {
				routeDefault := Rule{
					Action:   "router_chain",
					Chain:    table,
					Proto:    "all",
					IfaceIn:  "*",
					IfaceOut: "*",
					IPSrc:    "::_0",
					IPDst:    "::_0",
					Sports:   "0",
					Dports:   "0",
					Position: "?",
				}
				ruleexists, err := client.rulesAPIV6(ctx, routeDefault, "GET")
				if err != nil {
					return nil, fmt.Errorf("check default rules v6 %s failed : %s", table, err)
				}
				if !ruleexists {
					resp, err := client.rulesAPIV6(ctx, routeDefault, "PUT")
					if !resp || err != nil {
						return nil, fmt.Errorf("create default rules v6 %s failed : %s", table, err)
					}
				}
				if !c.noAddDefaultDrop {
					ruleDrop := Rule{
						Action:   "DROP",
						Chain:    table,
						Proto:    "all",
						IfaceIn:  "*",
						IfaceOut: "*",
						IPSrc:    "::_0",
						IPDst:    "::_0",
						Sports:   "0",
						Dports:   "0",
						Position: "?",
					}
					ruleexists, err = client.rulesAPIV6(ctx, ruleDrop, "GET")
					if err != nil {
						return nil, fmt.Errorf("check default rules drop v6 %s failed : %s", table, err)
					}
					if !ruleexists {
						resp, err := client.rulesAPIV6(ctx, ruleDrop, "PUT")
						if !resp || err != nil {
							return nil, fmt.Errorf("create default rules drop v6 %s failed : %s", table, err)
						}
					}
				}
			}
		}
	}

	return client, nil
}

func (client *Client) newRequest(ctx context.Context, method string, uriString string) (*http.Request, error) {
	IP := client.FirewallIP
	port := strconv.Itoa(client.Port)

	matched := strings.Contains(uriString, "?")
	var urLString string
	if matched {
		urLString = "http://" + IP + ":" + port + uriString + "&logname=" + client.Logname
	} else {
		urLString = "http://" + IP + ":" + port + uriString + "?&logname=" + client.Logname
	}
	if client.HTTPS {
		urLString = strings.ReplaceAll(urLString, "http://", "https://")
	}
	req, err := http.NewRequestWithContext(ctx, method, urLString, nil)
	if client.Login != "" && client.Password != "" {
		req.SetBasicAuth(client.Login, client.Password)
	}
	log.Printf("[INFO] New API request: %s %s", method, urLString)
	if err != nil {
		return nil, fmt.Errorf("error during creation of request: %s", err)
	}

	return req, nil
}

func (client *Client) rulesAPI(ctx context.Context, version string, rule Rule, method string) (bool, error) {
	var uriString []string
	if version == "v4" {
		uriString = append(uriString, "/rules/")
	}
	if version == "v6" {
		uriString = append(uriString, "/rules_v6/")
	}
	uriString = append(uriString, rule.Action, "/", rule.Chain, "/", rule.Proto, "/", rule.IfaceIn, "/", rule.IfaceOut,
		"/", rule.IPSrc, "/", rule.IPDst, "/")
	if (rule.Sports != "0") || (rule.Dports != "0") || (rule.State != "") ||
		(rule.Icmptype != "") || rule.Fragment || (rule.Position != "?") || (rule.Logprefix != "") {
		uriString = append(uriString, "?")
		if rule.Sports != "0" {
			uriString = append(uriString, "&sports=", rule.Sports)
		}
		if rule.Dports != "0" {
			uriString = append(uriString, "&dports=", rule.Dports)
		}
		if rule.State != "" {
			uriString = append(uriString, "&state=", rule.State)
		}
		if rule.Icmptype != "" {
			uriString = append(uriString, "&icmptype=", rule.Icmptype)
		}
		if rule.Fragment {
			uriString = append(uriString, "&fragment=true")
		}
		if rule.Position != "?" {
			uriString = append(uriString, "&position=", rule.Position)
		}
		if rule.Logprefix != "" {
			uriString = append(uriString, "&log-prefix=", rule.Logprefix)
		}
	}

	req, err := client.newRequest(ctx, method, strings.Join(uriString, ""))
	if err != nil {
		return false, err
	}
	tr := &http.Transport{
		DisableKeepAlives: true,
	}
	if client.Insecure {
		tr = &http.Transport{
			TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
			DisableKeepAlives: true,
		}
	}
	httpClient := &http.Client{Transport: tr}
	resp, err := httpClient.Do(req)
	if err != nil {
		log.Printf("error when do request %s", err)

		return false, err
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	log.Printf("[INFO] Response API request %d %s", resp.StatusCode, string(body))

	if resp.StatusCode == http.StatusOK {
		return true, nil
	}
	if resp.StatusCode == http.StatusNotFound {
		return false, nil
	}
	if resp.StatusCode == http.StatusConflict {
		return false, errors.New("conflict with position")
	}

	return false, errors.New(string(body))
}

func (client *Client) natAPI(ctx context.Context, version string, rule Rule, method string) (bool, error) {
	var uriString []string
	if version == "v4" {
		uriString = append(uriString, "/nat/")
	}
	if version == "v6" {
		uriString = append(uriString, "/nat_v6/")
	}
	uriString = append(uriString, rule.Action, "/", rule.Chain, "/", rule.Proto, "/", rule.Iface, "/",
		rule.IPSrc, "/", rule.IPDst, "/", rule.IPNat, "/")

	if (rule.Dports != "0") || (rule.Position != "?") || (rule.NthEvery != "") || rule.Except {
		uriString = append(uriString, "?")
		if rule.Dports != "0" {
			uriString = append(uriString, "&dport=", rule.Dports)
		}
		if rule.Position != "?" {
			uriString = append(uriString, "&position=", rule.Position)
		}
		if rule.NthEvery != "" {
			uriString = append(uriString, "&nth_every=", rule.NthEvery, "&nth_packet=", rule.NthPacket)
		}
		if rule.Except {
			uriString = append(uriString, "&except=true")
		}
	}

	req, err := client.newRequest(ctx, method, strings.Join(uriString, ""))
	if err != nil {
		return false, err
	}
	tr := &http.Transport{
		DisableKeepAlives: true,
	}
	if client.Insecure {
		tr = &http.Transport{
			TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
			DisableKeepAlives: true,
		}
	}
	httpClient := &http.Client{Transport: tr}
	resp, err := httpClient.Do(req)
	if err != nil {
		log.Printf("rrror when do request %s", err)

		return false, err
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	log.Printf("[INFO] Response API request %d %s", resp.StatusCode, string(body))

	if resp.StatusCode == http.StatusOK {
		return true, nil
	}
	if resp.StatusCode == http.StatusNotFound {
		return false, nil
	}
	if resp.StatusCode == http.StatusConflict {
		return false, errors.New("conflict with position")
	}

	return false, errors.New(string(body))
}

func (client *Client) rawAPI(ctx context.Context, version string, rule Rule, method string) (bool, error) {
	var uriString []string
	if version == "v4" {
		uriString = append(uriString, "/raw/")
	}
	if version == "v6" {
		uriString = append(uriString, "/raw_v6/")
	}
	uriString = append(uriString, rule.Action, "/", rule.Chain, "/", rule.Proto, "/", rule.IfaceIn, "/",
		rule.IfaceOut, "/", rule.IPSrc, "/", rule.IPDst, "/")
	if (rule.Sports != "0") || (rule.Dports != "0") || (rule.Tcpflags1 != "") || (rule.Tcpflags2 != "") ||
		rule.Notrack || (rule.Position != "?") || (rule.Logprefix != "") || (rule.Tcpmss != "") {
		uriString = append(uriString, "?")
		if rule.Sports != "0" {
			uriString = append(uriString, "&sports=", rule.Sports)
		}
		if rule.Dports != "0" {
			uriString = append(uriString, "&dports=", rule.Dports)
		}
		if (rule.Tcpflags1 != "") && (rule.Tcpflags2 != "") {
			uriString = append(uriString, "&tcpflag1=", rule.Tcpflags1, "&tcpflag2=", rule.Tcpflags2)
		}
		if rule.Notrack {
			uriString = append(uriString, "&notrack=true")
		}
		if rule.Position != "?" {
			uriString = append(uriString, "&position=", rule.Position)
		}
		if rule.Logprefix != "" {
			uriString = append(uriString, "&log-prefix=", rule.Logprefix)
		}
		if rule.Tcpmss != "" {
			uriString = append(uriString, "&tcpmss=", rule.Tcpmss)
		}
	}

	req, err := client.newRequest(ctx, method, strings.Join(uriString, ""))
	if err != nil {
		return false, err
	}
	tr := &http.Transport{
		DisableKeepAlives: true,
	}
	if client.Insecure {
		tr = &http.Transport{
			TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
			DisableKeepAlives: true,
		}
	}
	httpClient := &http.Client{Transport: tr}
	resp, err := httpClient.Do(req)
	if err != nil {
		log.Printf("error when do request %s", err)

		return false, err
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	log.Printf("[INFO] Response API request %d %s", resp.StatusCode, string(body))

	if resp.StatusCode == http.StatusOK {
		return true, nil
	}
	if resp.StatusCode == http.StatusNotFound {
		return false, nil
	}
	if resp.StatusCode == http.StatusConflict {
		return false, errors.New("conflict with position")
	}

	return false, errors.New(string(body))
}

func (client *Client) chainAPI(ctx context.Context, version string, chain string, method string) (bool, error) {
	var uriString []string
	if version == "v4" {
		uriString = append(uriString, "/chain/filter/")
	}
	if version == "v6" {
		uriString = append(uriString, "/chain_v6/filter/")
	}
	uriString = append(uriString, chain, "/")

	req, err := client.newRequest(ctx, method, strings.Join(uriString, ""))
	if err != nil {
		return false, err
	}
	tr := &http.Transport{
		DisableKeepAlives: true,
	}
	if client.Insecure {
		tr = &http.Transport{
			TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
			DisableKeepAlives: true,
		}
	}
	httpClient := &http.Client{Transport: tr}
	resp, err := httpClient.Do(req)
	if err != nil {
		log.Printf("error when do request %s", err)

		return false, err
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	log.Printf("[INFO] Response API request %d %s", resp.StatusCode, string(body))

	if resp.StatusCode == http.StatusOK {
		return true, nil
	}
	if resp.StatusCode == http.StatusBadRequest {
		return false, nil
	}
	if resp.StatusCode == http.StatusUnauthorized {
		return false, errors.New(strings.Join([]string{client.FirewallIP, ": You are Unauthorized"}, " "))
	}

	return false, errors.New(string(body))
}

func (client *Client) save(ctx context.Context, version string) error {
	var uriString []string
	if version == "v4" {
		uriString = append(uriString, "/save/")
	}
	if version == "v6" {
		uriString = append(uriString, "/save_v6/")
	}

	req, err := client.newRequest(ctx, "GET", strings.Join(uriString, ""))
	if err != nil {
		return err
	}
	tr := &http.Transport{
		DisableKeepAlives: true,
	}
	if client.Insecure {
		tr = &http.Transport{
			TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
			DisableKeepAlives: true,
		}
	}
	httpClient := &http.Client{Transport: tr}
	resp, err := httpClient.Do(req)
	if err != nil {
		log.Printf("error when do request %s", err)

		return err
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	log.Printf("[INFO] Response API request %d %s", resp.StatusCode, string(body))
	if resp.StatusCode != http.StatusOK {
		return errors.New(string(body))
	}

	return nil
}

func (client *Client) chainAPIV4(ctx context.Context, chain string, method string) (bool, error) {
	return client.chainAPI(ctx, "v4", chain, method)
}

func (client *Client) rulesAPIV4(ctx context.Context, rule Rule, method string) (bool, error) {
	return client.rulesAPI(ctx, "v4", rule, method)
}

func (client *Client) natAPIV4(ctx context.Context, rule Rule, method string) (bool, error) {
	return client.natAPI(ctx, "v4", rule, method)
}

func (client *Client) rawAPIV4(ctx context.Context, rule Rule, method string) (bool, error) {
	return client.rawAPI(ctx, "v4", rule, method)
}

func (client *Client) saveV4(ctx context.Context) error {
	return client.save(ctx, "v4")
}

func (client *Client) chainAPIV6(ctx context.Context, chain string, method string) (bool, error) {
	return client.chainAPI(ctx, "v6", chain, method)
}

func (client *Client) rulesAPIV6(ctx context.Context, rule Rule, method string) (bool, error) {
	return client.rulesAPI(ctx, "v6", rule, method)
}

func (client *Client) natAPIV6(ctx context.Context, rule Rule, method string) (bool, error) {
	return client.natAPI(ctx, "v6", rule, method)
}

func (client *Client) rawAPIV6(ctx context.Context, rule Rule, method string) (bool, error) {
	return client.rawAPI(ctx, "v6", rule, method)
}

func (client *Client) saveV6(ctx context.Context) error {
	return client.save(ctx, "v6")
}


<|start_filename|>iptables/config.go<|end_filename|>
package iptables

import (
	"context"
	"log"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	vaultapi "github.com/hashicorp/vault/api"
)

// Config provider.
type Config struct {
	https            bool
	ipv6Enable       bool
	insecure         bool
	vaultEnable      bool
	noAddDefaultDrop bool
	firewallPortAPI  int
	firewallIP       string
	logname          string
	login            string
	password         string
	vaultPath        string
	vaultKey         string
	allowedIPs       []interface{}
}

// Client configures with Config.
func (c *Config) Client(ctx context.Context) (*Client, diag.Diagnostics) {
	var client *Client
	var err error
	if !c.vaultEnable {
		client, err = NewClient(ctx, c, c.login, c.password)
	} else {
		login, password := getloginVault(c.vaultPath, c.firewallIP, c.vaultKey)
		client, err = NewClient(ctx, c, login, password)
	}
	if err != nil {
		return nil, diag.FromErr(err)
	}

	log.Printf("[INFO] Firewall client configured for server %s", c.firewallIP)

	return client, nil
}

func getloginVault(path string, firewallIP string, key string) (string, string) {
	login := ""
	password := ""
	client, err := vaultapi.NewClient(vaultapi.DefaultConfig())
	if err != nil {
		return "", ""
	}

	c := client.Logical()
	if key != "" {
		secret, err := c.Read(strings.Join([]string{"/secret/", path, "/", key}, ""))
		if err != nil {
			return "", ""
		}
		if secret != nil {
			for key, value := range secret.Data {
				if key == "login" {
					login = value.(string)
				}
				if key == "password" {
					password = value.(string)
				}
			}
		}
	} else {
		secret, err := c.Read(strings.Join([]string{"/secret/", path, "/", firewallIP}, ""))
		if err != nil {
			return "", ""
		}
		if secret != nil {
			for key, value := range secret.Data {
				if key == "login" {
					login = value.(string)
				}
				if key == "password" {
					password = value.(string)
				}
			}
		}
	}

	return login, password
}
