//go:build linux

package net

import (
	
	
	
	
	
	
	
	
	
	
	
	

	
)

const ( // Conntrack Column numbers
	CT_ENTRIES = iota
	CT_SEARCHED
	CT_FOUND
	CT_NEW
	CT_INVALID
	CT_IGNORE
	CT_DELETE
	CT_DELETE_LIST
	CT_INSERT
	CT_INSERT_FAILED
	CT_DROP
	CT_EARLY_DROP
	CT_ICMP_ERROR
	CT_EXPECT_NEW
	CT_EXPECT_CREATE
	CT_EXPECT_DELETE
	CT_SEARCH_RESTART
)

// NetIOCounters returnes network I/O statistics for every network
// interface installed on the system.  If pernic argument is false,
// return only sum of all information (which name is 'all'). If true,
// every network interface installed on the system is returned
// separately.
func ( bool) ([]IOCountersStat, error) {
	return IOCountersWithContext(context.Background(), )
}

func ( context.Context,  bool) ([]IOCountersStat, error) {
	 := common.HostProc("net/dev")
	return IOCountersByFileWithContext(, , )
}

func ( bool,  string) ([]IOCountersStat, error) {
	return IOCountersByFileWithContext(context.Background(), , )
}

func ( context.Context,  bool,  string) ([]IOCountersStat, error) {
	,  := common.ReadLines()
	if  != nil {
		return nil, 
	}

	 := make([]string, 2)

	 := len() - 1

	 := make([]IOCountersStat, 0, )

	for ,  := range [2:] {
		 := strings.LastIndex(, ":")
		if  == -1 {
			continue
		}
		[0] = [0:]
		[1] = [+1:]

		 := strings.TrimSpace([0])
		if  == "" {
			continue
		}

		 := strings.Fields(strings.TrimSpace([1]))
		,  := strconv.ParseUint([0], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([1], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([2], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([3], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([4], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([8], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([9], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([10], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([11], 10, 64)
		if  != nil {
			return , 
		}
		,  := strconv.ParseUint([12], 10, 64)
		if  != nil {
			return , 
		}

		 := IOCountersStat{
			Name:        ,
			BytesRecv:   ,
			PacketsRecv: ,
			Errin:       ,
			Dropin:      ,
			Fifoin:      ,
			BytesSent:   ,
			PacketsSent: ,
			Errout:      ,
			Dropout:     ,
			Fifoout:     ,
		}
		 = append(, )
	}

	if ! {
		return getIOCountersAll()
	}

	return , nil
}

var netProtocols = []string{
	"ip",
	"icmp",
	"icmpmsg",
	"tcp",
	"udp",
	"udplite",
}

// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.
// Available protocols:
//
//	ip,icmp,icmpmsg,tcp,udp,udplite
func ( []string) ([]ProtoCountersStat, error) {
	return ProtoCountersWithContext(context.Background(), )
}

func ( context.Context,  []string) ([]ProtoCountersStat, error) {
	if len() == 0 {
		 = netProtocols
	}

	 := make([]ProtoCountersStat, 0, len())
	 := make(map[string]bool, len())
	for ,  := range  {
		[] = true
	}

	 := common.HostProc("net/snmp")
	,  := common.ReadLines()
	if  != nil {
		return nil, 
	}

	 := len()
	for  := 0;  < ; ++ {
		 := []
		 := strings.IndexRune(, ':')
		if  == -1 {
			return nil, errors.New( + " is not fomatted correctly, expected ':'.")
		}
		 := strings.ToLower([:])
		if ![] {
			// skip protocol and data line
			++
			continue
		}

		// Read header line
		 := strings.Split([+2:], " ")

		// Read data line
		++
		 := strings.Split([][+2:], " ")
		if len() != len() {
			return nil, errors.New( + " is not fomatted correctly, expected same number of columns.")
		}
		 := ProtoCountersStat{
			Protocol: ,
			Stats:    make(map[string]int64, len()),
		}
		for  := range  {
			,  := strconv.ParseInt([], 10, 64)
			if  != nil {
				return nil, 
			}
			.Stats[[]] = 
		}
		 = append(, )
	}
	return , nil
}

// NetFilterCounters returns iptables conntrack statistics
// the currently in use conntrack count and the max.
// If the file does not exist or is invalid it will return nil.
func () ([]FilterStat, error) {
	return FilterCountersWithContext(context.Background())
}

func ( context.Context) ([]FilterStat, error) {
	 := common.HostProc("sys/net/netfilter/nf_conntrack_count")
	 := common.HostProc("sys/net/netfilter/nf_conntrack_max")

	,  := common.ReadInts()
	if  != nil {
		return nil, 
	}
	 := make([]FilterStat, 0, 1)

	,  := common.ReadInts()
	if  != nil {
		return nil, 
	}

	 := FilterStat{
		ConnTrackCount: [0],
		ConnTrackMax:   [0],
	}

	 = append(, )
	return , nil
}

// ConntrackStats returns more detailed info about the conntrack table
func ( bool) ([]ConntrackStat, error) {
	return ConntrackStatsWithContext(context.Background(), )
}

// ConntrackStatsWithContext returns more detailed info about the conntrack table
func ( context.Context,  bool) ([]ConntrackStat, error) {
	return conntrackStatsFromFile(common.HostProc("net/stat/nf_conntrack"), )
}

// conntrackStatsFromFile returns more detailed info about the conntrack table
// from `filename`
// If 'percpu' is false, the result will contain exactly one item with totals/summary
func conntrackStatsFromFile( string,  bool) ([]ConntrackStat, error) {
	,  := common.ReadLines()
	if  != nil {
		return nil, 
	}

	 := NewConntrackStatList()

	for ,  := range  {
		 := strings.Fields()
		if len() == 17 && [0] != "entries" {
			.Append(NewConntrackStat(
				common.HexToUint32([CT_ENTRIES]),
				common.HexToUint32([CT_SEARCHED]),
				common.HexToUint32([CT_FOUND]),
				common.HexToUint32([CT_NEW]),
				common.HexToUint32([CT_INVALID]),
				common.HexToUint32([CT_IGNORE]),
				common.HexToUint32([CT_DELETE]),
				common.HexToUint32([CT_DELETE_LIST]),
				common.HexToUint32([CT_INSERT]),
				common.HexToUint32([CT_INSERT_FAILED]),
				common.HexToUint32([CT_DROP]),
				common.HexToUint32([CT_EARLY_DROP]),
				common.HexToUint32([CT_ICMP_ERROR]),
				common.HexToUint32([CT_EXPECT_NEW]),
				common.HexToUint32([CT_EXPECT_CREATE]),
				common.HexToUint32([CT_EXPECT_DELETE]),
				common.HexToUint32([CT_SEARCH_RESTART]),
			))
		}
	}

	if  {
		return .Items(), nil
	}
	return .Summary(), nil
}

// http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h
var TCPStatuses = map[string]string{
	"01": "ESTABLISHED",
	"02": "SYN_SENT",
	"03": "SYN_RECV",
	"04": "FIN_WAIT1",
	"05": "FIN_WAIT2",
	"06": "TIME_WAIT",
	"07": "CLOSE",
	"08": "CLOSE_WAIT",
	"09": "LAST_ACK",
	"0A": "LISTEN",
	"0B": "CLOSING",
}

type netConnectionKindType struct {
	family   uint32
	sockType uint32
	filename string
}

var kindTCP4 = netConnectionKindType{
	family:   syscall.AF_INET,
	sockType: syscall.SOCK_STREAM,
	filename: "tcp",
}

var kindTCP6 = netConnectionKindType{
	family:   syscall.AF_INET6,
	sockType: syscall.SOCK_STREAM,
	filename: "tcp6",
}

var kindUDP4 = netConnectionKindType{
	family:   syscall.AF_INET,
	sockType: syscall.SOCK_DGRAM,
	filename: "udp",
}

var kindUDP6 = netConnectionKindType{
	family:   syscall.AF_INET6,
	sockType: syscall.SOCK_DGRAM,
	filename: "udp6",
}

var kindUNIX = netConnectionKindType{
	family:   syscall.AF_UNIX,
	filename: "unix",
}

var netConnectionKindMap = map[string][]netConnectionKindType{
	"all":   {kindTCP4, kindTCP6, kindUDP4, kindUDP6, kindUNIX},
	"tcp":   {kindTCP4, kindTCP6},
	"tcp4":  {kindTCP4},
	"tcp6":  {kindTCP6},
	"udp":   {kindUDP4, kindUDP6},
	"udp4":  {kindUDP4},
	"udp6":  {kindUDP6},
	"unix":  {kindUNIX},
	"inet":  {kindTCP4, kindTCP6, kindUDP4, kindUDP6},
	"inet4": {kindTCP4, kindUDP4},
	"inet6": {kindTCP6, kindUDP6},
}

type inodeMap struct {
	pid int32
	fd  uint32
}

type connTmp struct {
	fd       uint32
	family   uint32
	sockType uint32
	laddr    Addr
	raddr    Addr
	status   string
	pid      int32
	boundPid int32
	path     string
}

// Return a list of network connections opened.
func ( string) ([]ConnectionStat, error) {
	return ConnectionsWithContext(context.Background(), )
}

func ( context.Context,  string) ([]ConnectionStat, error) {
	return ConnectionsPid(, 0)
}

// Return a list of network connections opened returning at most `max`
// connections for each running process.
func ( string,  int) ([]ConnectionStat, error) {
	return ConnectionsMaxWithContext(context.Background(), , )
}

func ( context.Context,  string,  int) ([]ConnectionStat, error) {
	return ConnectionsPidMax(, 0, )
}

// Return a list of network connections opened, omitting `Uids`.
// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
// removed from the API in the future.
func ( string) ([]ConnectionStat, error) {
	return ConnectionsWithoutUidsWithContext(context.Background(), )
}

func ( context.Context,  string) ([]ConnectionStat, error) {
	return ConnectionsMaxWithoutUidsWithContext(, , 0)
}

func ( context.Context,  string,  int) ([]ConnectionStat, error) {
	return ConnectionsPidMaxWithoutUidsWithContext(, , 0, )
}

// Return a list of network connections opened by a process.
func ( string,  int32) ([]ConnectionStat, error) {
	return ConnectionsPidWithContext(context.Background(), , )
}

func ( string,  int32) ([]ConnectionStat, error) {
	return ConnectionsPidWithoutUidsWithContext(context.Background(), , )
}

func ( context.Context,  string,  int32) ([]ConnectionStat, error) {
	return ConnectionsPidMaxWithContext(, , , 0)
}

func ( context.Context,  string,  int32) ([]ConnectionStat, error) {
	return ConnectionsPidMaxWithoutUidsWithContext(, , , 0)
}

// Return up to `max` network connections opened by a process.
func ( string,  int32,  int) ([]ConnectionStat, error) {
	return ConnectionsPidMaxWithContext(context.Background(), , , )
}

func ( string,  int32,  int) ([]ConnectionStat, error) {
	return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), , , )
}

func ( context.Context,  string,  int32,  int) ([]ConnectionStat, error) {
	return connectionsPidMaxWithoutUidsWithContext(, , , , false)
}

func ( context.Context,  string,  int32,  int) ([]ConnectionStat, error) {
	return connectionsPidMaxWithoutUidsWithContext(, , , , true)
}

func connectionsPidMaxWithoutUidsWithContext( context.Context,  string,  int32,  int,  bool) ([]ConnectionStat, error) {
	,  := netConnectionKindMap[]
	if ! {
		return nil, fmt.Errorf("invalid kind, %s", )
	}
	 := common.HostProc()
	var  error
	var  map[string][]inodeMap
	if  == 0 {
		,  = getProcInodesAll(, )
	} else {
		,  = getProcInodes(, , )
		if len() == 0 {
			// no connection for the pid
			return []ConnectionStat{}, nil
		}
	}
	if  != nil {
		return nil, fmt.Errorf("cound not get pid(s), %d: %s", , )
	}
	return statsFromInodes(, , , , )
}

func statsFromInodes( string,  int32,  []netConnectionKindType,  map[string][]inodeMap,  bool) ([]ConnectionStat, error) {
	 := make(map[string]struct{})
	var  []ConnectionStat

	var  error
	for ,  := range  {
		var  string
		var  string
		var  []connTmp
		if  == 0 {
			 = fmt.Sprintf("%s/net/%s", , .filename)
		} else {
			 = fmt.Sprintf("%s/%d/net/%s", , , .filename)
		}
		switch .family {
		case syscall.AF_INET, syscall.AF_INET6:
			,  = processInet(, , , )
		case syscall.AF_UNIX:
			,  = processUnix(, , , )
		}
		if  != nil {
			return nil, 
		}
		for ,  := range  {
			// Build TCP key to id the connection uniquely
			// socket type, src ip, src port, dst ip, dst port and state should be enough
			// to prevent duplications.
			 = fmt.Sprintf("%d-%s:%d-%s:%d-%s", .sockType, .laddr.IP, .laddr.Port, .raddr.IP, .raddr.Port, .status)
			if ,  := [];  {
				continue
			}

			 := ConnectionStat{
				Fd:     .fd,
				Family: .family,
				Type:   .sockType,
				Laddr:  .laddr,
				Raddr:  .raddr,
				Status: .status,
				Pid:    .pid,
			}
			if .pid == 0 {
				.Pid = .boundPid
			} else {
				.Pid = .pid
			}

			if ! {
				// fetch process owner Real, effective, saved set, and filesystem UIDs
				 := process{Pid: .Pid}
				.Uids, _ = .getUids()
			}

			 = append(, )
			[] = struct{}{}
		}

	}

	return , nil
}

// getProcInodes returnes fd of the pid.
func getProcInodes( string,  int32,  int) (map[string][]inodeMap, error) {
	 := make(map[string][]inodeMap)

	 := fmt.Sprintf("%s/%d/fd", , )
	,  := os.Open()
	if  != nil {
		return , 
	}
	defer .Close()
	,  := .Readdir()
	if  != nil {
		return , 
	}
	for ,  := range  {
		 := fmt.Sprintf("%s/%d/fd/%s", , , .Name())

		,  := os.Readlink()
		if  != nil {
			continue
		}
		if !strings.HasPrefix(, "socket:[") {
			continue
		}
		// the process is using a socket
		 := len()
		 = [8 : -1]
		,  := []
		if ! {
			[] = make([]inodeMap, 0)
		}
		,  := strconv.Atoi(.Name())
		if  != nil {
			continue
		}

		 := inodeMap{
			pid: ,
			fd:  uint32(),
		}
		[] = append([], )
	}
	return , nil
}

// Pids retunres all pids.
// Note: this is a copy of process_linux.Pids()
// FIXME: Import process occures import cycle.
// move to common made other platform breaking. Need consider.
func () ([]int32, error) {
	return PidsWithContext(context.Background())
}

func ( context.Context) ([]int32, error) {
	var  []int32

	,  := os.Open(common.HostProc())
	if  != nil {
		return nil, 
	}
	defer .Close()

	,  := .Readdirnames(-1)
	if  != nil {
		return nil, 
	}
	for ,  := range  {
		,  := strconv.ParseInt(, 10, 32)
		if  != nil {
			// if not numeric name, just skip
			continue
		}
		 = append(, int32())
	}

	return , nil
}

// Note: the following is based off process_linux structs and methods
// we need these to fetch the owner of a process ID
// FIXME: Import process occures import cycle.
// see remarks on pids()
type process struct {
	Pid  int32 `json:"pid"`
	uids []int32
}

// Uids returns user ids of the process as a slice of the int
func ( *process) () ([]int32, error) {
	 := .fillFromStatus()
	if  != nil {
		return []int32{}, 
	}
	return .uids, nil
}

// Get status from /proc/(pid)/status
func ( *process) () error {
	 := .Pid
	 := common.HostProc(strconv.Itoa(int()), "status")
	,  := os.ReadFile()
	if  != nil {
		return 
	}
	 := strings.Split(string(), "\n")
	for ,  := range  {
		 := strings.SplitN(, "\t", 2)
		if len() < 2 {
			continue
		}
		 := [1]
		switch strings.TrimRight([0], ":") {
		case "Uid":
			.uids = make([]int32, 0, 4)
			for ,  := range strings.Split(, "\t") {
				,  := strconv.ParseInt(, 10, 32)
				if  != nil {
					return 
				}
				.uids = append(.uids, int32())
			}
		}
	}
	return nil
}

func getProcInodesAll( string,  int) (map[string][]inodeMap, error) {
	,  := Pids()
	if  != nil {
		return nil, 
	}
	 := make(map[string][]inodeMap)

	for ,  := range  {
		,  := getProcInodes(, , )
		if  != nil {
			// skip if permission error or no longer exists
			if os.IsPermission() || errors.Is(, fs.ErrNotExist) ||  == io.EOF {
				continue
			}
			return , 
		}
		if len() == 0 {
			continue
		}
		// TODO: update ret.
		 = updateMap(, )
	}
	return , nil
}

// decodeAddress decode addresse represents addr in proc/net/*
// ex:
// "0500000A:0016" -> "10.0.0.5", 22
// "0085002452100113070057A13F025401:0035" -> "2400:8500:1301:1052:a157:7:154:23f", 53
func decodeAddress( uint32,  string) (Addr, error) {
	 := strings.Split(, ":")
	if len() != 2 {
		return Addr{}, fmt.Errorf("does not contain port, %s", )
	}
	 := [0]
	,  := strconv.ParseUint([1], 16, 16)
	if  != nil {
		return Addr{}, fmt.Errorf("invalid port, %s", )
	}
	,  := hex.DecodeString()
	if  != nil {
		return Addr{}, fmt.Errorf("decode error, %s", )
	}
	var  net.IP
	// Assumes this is little_endian
	if  == syscall.AF_INET {
		 = net.IP(Reverse())
	} else { // IPv6
		,  = parseIPv6HexString()
		if  != nil {
			return Addr{}, 
		}
	}
	return Addr{
		IP:   .String(),
		Port: uint32(),
	}, nil
}

// Reverse reverses array of bytes.
func ( []byte) []byte {
	return ReverseWithContext(context.Background(), )
}

func ( context.Context,  []byte) []byte {
	for ,  := 0, len()-1;  < ; ,  = +1, -1 {
		[], [] = [], []
	}
	return 
}

// parseIPv6HexString parse array of bytes to IPv6 string
func parseIPv6HexString( []byte) (net.IP, error) {
	if len() != 16 {
		return nil, fmt.Errorf("invalid IPv6 string")
	}

	 := make([]byte, 0, 16)
	for  := 0;  < len();  += 4 {
		 := Reverse([ : +4])
		 = append(, ...)
	}
	return net.IP(), nil
}

func processInet( string,  netConnectionKindType,  map[string][]inodeMap,  int32) ([]connTmp, error) {
	if strings.HasSuffix(, "6") && !common.PathExists() {
		// IPv6 not supported, return empty.
		return []connTmp{}, nil
	}

	// Read the contents of the /proc file with a single read sys call.
	// This minimizes duplicates in the returned connections
	// For more info:
	// https://github.com/shirou/gopsutil/pull/361
	,  := os.ReadFile()
	if  != nil {
		return nil, 
	}

	 := bytes.Split(, []byte("\n"))

	var  []connTmp
	// skip first line
	for ,  := range [1:] {
		 := strings.Fields(string())
		if len() < 10 {
			continue
		}
		 := [1]
		 := [2]
		 := [3]
		 := [9]
		 := int32(0)
		 := uint32(0)
		,  := []
		if  {
			 = [0].pid
			 = [0].fd
		}
		if  > 0 &&  !=  {
			continue
		}
		if .sockType == syscall.SOCK_STREAM {
			 = TCPStatuses[]
		} else {
			 = "NONE"
		}
		,  := decodeAddress(.family, )
		if  != nil {
			continue
		}
		,  := decodeAddress(.family, )
		if  != nil {
			continue
		}

		 = append(, connTmp{
			fd:       ,
			family:   .family,
			sockType: .sockType,
			laddr:    ,
			raddr:    ,
			status:   ,
			pid:      ,
		})
	}

	return , nil
}

func processUnix( string,  netConnectionKindType,  map[string][]inodeMap,  int32) ([]connTmp, error) {
	// Read the contents of the /proc file with a single read sys call.
	// This minimizes duplicates in the returned connections
	// For more info:
	// https://github.com/shirou/gopsutil/pull/361
	,  := os.ReadFile()
	if  != nil {
		return nil, 
	}

	 := bytes.Split(, []byte("\n"))

	var  []connTmp
	// skip first line
	for ,  := range [1:] {
		 := strings.Fields(string())
		if len() < 6 {
			continue
		}
		,  := strconv.Atoi([4])
		if  != nil {
			return nil, 
		}

		 := [6]

		var  []inodeMap
		,  := []
		if ! {
			 = []inodeMap{
				{},
			}
		}
		for ,  := range  {
			if  > 0 &&  != .pid {
				continue
			}
			var  string
			if len() == 8 {
				 = [len()-1]
			}
			 = append(, connTmp{
				fd:       .fd,
				family:   .family,
				sockType: uint32(),
				laddr: Addr{
					IP: ,
				},
				pid:    .pid,
				status: "NONE",
				path:   ,
			})
		}
	}

	return , nil
}

func updateMap(,  map[string][]inodeMap) map[string][]inodeMap {
	for ,  := range  {
		,  := []
		if ! {
			[] = 
			continue
		}
		[] = append(, ...)
	}
	return 
}