<commit_before>package drivers

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"text/template"

	"github.com/pborman/uuid"

	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/validate"
	"github.com/lxc/lxd/shared/version"
)

const nftablesNamespace = "lxd"
const nftablesContentTemplate = "nftablesContent"

// nftablesChainSeparator The "." character is specifically chosen here so as to prevent the ability for collisions
// between project prefix (which is empty if project is default) and device name combinations that both are allowed
// to contain underscores (where as instance name is not).
const nftablesChainSeparator = "."

// nftablesMinVersion We need at least 0.9.1 as this was when the arp ether saddr filters were added.
const nftablesMinVersion = "0.9.1"

// Nftables is an implmentation of LXD firewall using nftables.
type Nftables struct{}

// String returns the driver name.
func (d Nftables) String() string {
	return "nftables"
}

// Compat returns whether the driver backend is in use, and any host compatibility errors.
func (d Nftables) Compat() (bool, error) {
	// Get the kernel version.
	uname, err := shared.Uname()
	if err != nil {
		return false, err
	}

	// We require a >= 5.2 kernel to avoid weird conflicts with xtables and support for inet table NAT rules.
	releaseLen := len(uname.Release)
	if releaseLen > 1 {
		verErr := fmt.Errorf("Kernel version does not meet minimum requirement of 5.2")
		releaseParts := strings.SplitN(uname.Release, ".", 3)
		if len(releaseParts) < 2 {
			return false, fmt.Errorf("Failed parsing kernel version number into parts: %w", err)
		}

		majorVer := releaseParts[0]
		majorVerInt, err := strconv.Atoi(majorVer)
		if err != nil {
			return false, fmt.Errorf("Failed parsing kernel major version number %q: %w", majorVer, err)
		}

		if majorVerInt < 5 {
			return false, verErr
		}

		if majorVerInt == 5 {
			minorVer := releaseParts[1]
			minorVerInt, err := strconv.Atoi(minorVer)
			if err != nil {
				return false, fmt.Errorf("Failed parsing kernel minor version number %q: %w", minorVer, err)
			}

			if minorVerInt < 2 {
				return false, verErr
			}
		}
	}

	// Check if nftables nft command exists, if not use xtables.
	_, err = exec.LookPath("nft")
	if err != nil {
		return false, fmt.Errorf("Backend command %q missing", "nft")
	}

	// Get nftables version.
	nftVersion, err := d.hostVersion()
	if err != nil {
		return false, fmt.Errorf("Failed detecting nft version: %w", err)
	}

	// Check nft version meets minimum required.
	minVer, _ := version.NewDottedVersion(nftablesMinVersion)
	if nftVersion.Compare(minVer) < 0 {
		return false, fmt.Errorf("nft version %q is too low, need %q or above", nftVersion, nftablesMinVersion)
	}

	// Check that nftables works at all (some kernels let you list ruleset despite missing support).
	testTable := fmt.Sprintf("lxd_test_%s", uuid.New())

	_, err = shared.RunCommandCLocale("nft", "create", "table", testTable)
	if err != nil {
		return false, fmt.Errorf("Failed to create a test table: %w", err)
	}

	_, err = shared.RunCommandCLocale("nft", "delete", "table", testTable)
	if err != nil {
		return false, fmt.Errorf("Failed to delete a test table: %w", err)
	}

	// Check whether in use by parsing ruleset and looking for existing rules.
	ruleset, err := d.nftParseRuleset()
	if err != nil {
		return false, fmt.Errorf("Failed parsing nftables existing ruleset: %w", err)
	}

	for _, item := range ruleset {
		if item.ItemType == "rule" {
			return true, nil // At least one rule found indicates in use.
		}
	}

	return false, nil
}

// nftGenericItem represents some common fields amongst the different nftables types.
type nftGenericItem struct {
	ItemType string `json:"-"`      // Type of item (table, chain or rule). Populated by LXD.
	Family   string `json:"family"` // Family of item (ip, ip6, bridge etc).
	Table    string `json:"table"`  // Table the item belongs to (for chains and rules).
	Chain    string `json:"chain"`  // Chain the item belongs to (for rules).
	Name     string `json:"name"`   // Name of item (for tables and chains).
}

// nftParseRuleset parses the ruleset and returns the generic parts as a slice of items.
func (d Nftables) nftParseRuleset() ([]nftGenericItem, error) {
	// Dump ruleset as JSON. Use -nn flags to avoid doing DNS lookups of IPs mentioned in any rules.
	cmd := exec.Command("nft", "--json", "-nn", "list", "ruleset")
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	defer func() { _ = cmd.Wait() }()

	// This only extracts certain generic parts of the ruleset, see man libnftables-json for more info.
	v := &struct {
		Nftables []map[string]nftGenericItem `json:"nftables"`
	}{}

	err = json.NewDecoder(stdout).Decode(v)
	if err != nil {
		return nil, err
	}

	items := []nftGenericItem{}
	for _, item := range v.Nftables {
		if rule, found := item["rule"]; found {
			rule.ItemType = "rule"
			items = append(items, rule)
		} else if chain, found := item["chain"]; found {
			chain.ItemType = "chain"
			items = append(items, chain)
		} else if table, found := item["table"]; found {
			table.ItemType = "table"
			items = append(items, table)
		}
	}

	err = cmd.Wait()<commit_msg>lxd/firewall/drivers: Removes all one-line assign and check statements.<commit_after>package drivers

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"text/template"

	"github.com/pborman/uuid"

	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/validate"
	"github.com/lxc/lxd/shared/version"
)

const nftablesNamespace = "lxd"
const nftablesContentTemplate = "nftablesContent"

// nftablesChainSeparator The "." character is specifically chosen here so as to prevent the ability for collisions
// between project prefix (which is empty if project is default) and device name combinations that both are allowed
// to contain underscores (where as instance name is not).
const nftablesChainSeparator = "."

// nftablesMinVersion We need at least 0.9.1 as this was when the arp ether saddr filters were added.
const nftablesMinVersion = "0.9.1"

// Nftables is an implmentation of LXD firewall using nftables.
type Nftables struct{}

// String returns the driver name.
func (d Nftables) String() string {
	return "nftables"
}

// Compat returns whether the driver backend is in use, and any host compatibility errors.
func (d Nftables) Compat() (bool, error) {
	// Get the kernel version.
	uname, err := shared.Uname()
	if err != nil {
		return false, err
	}

	// We require a >= 5.2 kernel to avoid weird conflicts with xtables and support for inet table NAT rules.
	releaseLen := len(uname.Release)
	if releaseLen > 1 {
		verErr := fmt.Errorf("Kernel version does not meet minimum requirement of 5.2")
		releaseParts := strings.SplitN(uname.Release, ".", 3)
		if len(releaseParts) < 2 {
			return false, fmt.Errorf("Failed parsing kernel version number into parts: %w", err)
		}

		majorVer := releaseParts[0]
		majorVerInt, err := strconv.Atoi(majorVer)
		if err != nil {
			return false, fmt.Errorf("Failed parsing kernel major version number %q: %w", majorVer, err)
		}

		if majorVerInt < 5 {
			return false, verErr
		}

		if majorVerInt == 5 {
			minorVer := releaseParts[1]
			minorVerInt, err := strconv.Atoi(minorVer)
			if err != nil {
				return false, fmt.Errorf("Failed parsing kernel minor version number %q: %w", minorVer, err)
			}

			if minorVerInt < 2 {
				return false, verErr
			}
		}
	}

	// Check if nftables nft command exists, if not use xtables.
	_, err = exec.LookPath("nft")
	if err != nil {
		return false, fmt.Errorf("Backend command %q missing", "nft")
	}

	// Get nftables version.
	nftVersion, err := d.hostVersion()
	if err != nil {
		return false, fmt.Errorf("Failed detecting nft version: %w", err)
	}

	// Check nft version meets minimum required.
	minVer, _ := version.NewDottedVersion(nftablesMinVersion)
	if nftVersion.Compare(minVer) < 0 {
		return false, fmt.Errorf("nft version %q is too low, need %q or above", nftVersion, nftablesMinVersion)
	}

	// Check that nftables works at all (some kernels let you list ruleset despite missing support).
	testTable := fmt.Sprintf("lxd_test_%s", uuid.New())

	_, err = shared.RunCommandCLocale("nft", "create", "table", testTable)
	if err != nil {
		return false, fmt.Errorf("Failed to create a test table: %w", err)
	}

	_, err = shared.RunCommandCLocale("nft", "delete", "table", testTable)
	if err != nil {
		return false, fmt.Errorf("Failed to delete a test table: %w", err)
	}

	// Check whether in use by parsing ruleset and looking for existing rules.
	ruleset, err := d.nftParseRuleset()
	if err != nil {
		return false, fmt.Errorf("Failed parsing nftables existing ruleset: %w", err)
	}

	for _, item := range ruleset {
		if item.ItemType == "rule" {
			return true, nil // At least one rule found indicates in use.
		}
	}

	return false, nil
}

// nftGenericItem represents some common fields amongst the different nftables types.
type nftGenericItem struct {
	ItemType string `json:"-"`      // Type of item (table, chain or rule). Populated by LXD.
	Family   string `json:"family"` // Family of item (ip, ip6, bridge etc).
	Table    string `json:"table"`  // Table the item belongs to (for chains and rules).
	Chain    string `json:"chain"`  // Chain the item belongs to (for rules).
	Name     string `json:"name"`   // Name of item (for tables and chains).
}

// nftParseRuleset parses the ruleset and returns the generic parts as a slice of items.
func (d Nftables) nftParseRuleset() ([]nftGenericItem, error) {
	// Dump ruleset as JSON. Use -nn flags to avoid doing DNS lookups of IPs mentioned in any rules.
	cmd := exec.Command("nft", "--json", "-nn", "list", "ruleset")
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	defer func() { _ = cmd.Wait() }()

	// This only extracts certain generic parts of the ruleset, see man libnftables-json for more info.
	v := &struct {
		Nftables []map[string]nftGenericItem `json:"nftables"`
	}{}

	err = json.NewDecoder(stdout).Decode(v)
	if err != nil {
		return nil, err
	}

	items := []nftGenericItem{}
	for _, item := range v.Nftables {
		rule, foundRule := item["rule"]
		chain, foundChain := item["chain"]
		table, foundTable := item["table"]
		if foundRule {
			rule.ItemType = "rule"
			items = append(items, rule)
		} else if foundChain {
			chain.ItemType = "chain"
			items = append(items, chain)
		} else if foundTable {
			table.ItemType = "table"
			items = append(items, table)
		}
	}

	err = cmd.Wait()
{'fraction_non_alphanumeric': 0.12273361227336123, 'fraction_numerical': 0.003719200371920037, 'mean_word_length': 3.632213608957795, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2126076', 'n_tokens_mistral': 3735, 'n_tokens_neox': 3502, 'n_words': 1234}