go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/port.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package resources
     5  
     6  import (
     7  	"bufio"
     8  	"encoding/binary"
     9  	"encoding/hex"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net/netip"
    14  	"regexp"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"unsafe"
    19  
    20  	"github.com/rs/zerolog/log"
    21  	"go.mondoo.com/cnquery/llx"
    22  	"go.mondoo.com/cnquery/providers-sdk/v1/plugin"
    23  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    24  	"go.mondoo.com/cnquery/providers/os/resources/lsof"
    25  	"go.mondoo.com/cnquery/providers/os/resources/ports"
    26  	"go.mondoo.com/cnquery/providers/os/resources/powershell"
    27  )
    28  
    29  type mqlPortsInternal struct {
    30  	processes2ports plugin.TValue[map[int64]*mqlProcess]
    31  	lock            sync.Mutex
    32  }
    33  
    34  func (p *mqlPorts) list() ([]interface{}, error) {
    35  	conn := p.MqlRuntime.Connection.(shared.Connection)
    36  	pf := conn.Asset().Platform
    37  
    38  	switch {
    39  	case pf.IsFamily("linux"):
    40  		return p.listLinux()
    41  	case pf.IsFamily("windows"):
    42  		return p.listWindows()
    43  
    44  	case pf.IsFamily("darwin") || pf.Name == "freebsd":
    45  		// both macOS and FreeBSD support lsof
    46  		// FreeBSD may need an installation via `pkg install sysutils/lsof`
    47  		return p.listMacos()
    48  	default:
    49  		return nil, errors.New("could not detect suitable ports manager for platform: " + pf.Name)
    50  	}
    51  }
    52  
    53  func (p *mqlPorts) listening() ([]interface{}, error) {
    54  	all := p.GetList()
    55  	if all.Error != nil {
    56  		return nil, all.Error
    57  	}
    58  
    59  	res := []interface{}{}
    60  	for i := range all.Data {
    61  		cur := all.Data[i]
    62  		port := cur.(*mqlPort)
    63  		if port.State.Data == "listen" {
    64  			res = append(res, cur)
    65  		}
    66  	}
    67  
    68  	return res, nil
    69  }
    70  
    71  // Linux Implementation
    72  
    73  var reLinuxProcNet = regexp.MustCompile(
    74  	"^\\s*\\d+: " +
    75  		"([0-9A-F]+):([0-9A-F]+) " + // local_address
    76  		"([0-9A-F]+):([0-9A-F]+) " + // rem_address
    77  		"([0-9A-F]+) " + // state
    78  		"[^ ]+:[^ ]+ " + // tx/rx
    79  		"[^ ]+:[^ ]+ " + // tr/tm
    80  		"[^ ]+\\s+" + // retrnsmt
    81  		"(\\d+)\\s+" + // uid
    82  		"\\d+\\s+" + // timeout
    83  		"(\\d+)\\s+" + // inode
    84  		"", // lots of other stuff if we want it...
    85  )
    86  
    87  // "lrwx------ 1 0 0 64 Dec  6 13:56 /proc/1/fd/12 -> socket:[37364]"
    88  var reFindSockets = regexp.MustCompile(
    89  	"^[lrwx-]+\\s+" +
    90  		"\\d+\\s+" +
    91  		"\\d+\\s+" + // uid
    92  		"\\d+\\s+" + // gid
    93  		"\\d+\\s+" +
    94  		"[^ ]+\\s+" + // month, e.g. Dec
    95  		"\\d+\\s+" + // day
    96  		"\\d+:\\d+\\s+" + // time
    97  		"/proc/(\\d+)/fd/\\d+\\s+" + // path
    98  		"->\\s+" +
    99  		".*socket:\\[(\\d+)\\].*\\s*") // target
   100  
   101  var TCP_STATES = map[int64]string{
   102  	1:  "established",
   103  	2:  "syn sent",
   104  	3:  "syn recv",
   105  	4:  "fin wait1",
   106  	5:  "fin wait2",
   107  	6:  "time wait",
   108  	7:  "close",
   109  	8:  "close wait",
   110  	9:  "last ack",
   111  	10: "listen",
   112  	11: "closing",
   113  	12: "new syn recv",
   114  }
   115  
   116  func hex2ipv4(s string) (string, error) {
   117  	a, err := strconv.ParseUint(s[0:2], 16, 0)
   118  	if err != nil {
   119  		return "", err
   120  	}
   121  
   122  	b, err := strconv.ParseUint(s[2:4], 16, 0)
   123  	if err != nil {
   124  		return "", err
   125  	}
   126  
   127  	c, err := strconv.ParseUint(s[4:6], 16, 0)
   128  	if err != nil {
   129  		return "", err
   130  	}
   131  
   132  	d, err := strconv.ParseUint(s[6:8], 16, 0)
   133  	if err != nil {
   134  		return "", err
   135  	}
   136  
   137  	return (strconv.FormatUint(d, 10) + "." +
   138  		strconv.FormatUint(c, 10) + "." +
   139  		strconv.FormatUint(b, 10) + "." +
   140  		strconv.FormatUint(a, 10)), nil
   141  }
   142  
   143  func hex2ipv6(s string) (string, error) {
   144  	networkEndian := ipv6EndianTranslation(s)
   145  	ipBytes, err := hex.DecodeString(networkEndian)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  
   150  	var ipBytes16 [16]byte
   151  
   152  	copy(ipBytes16[:], ipBytes)
   153  	ip := netip.AddrFrom16(ipBytes16)
   154  
   155  	if ip.Next().Is6() {
   156  		// ipv6-friendly formatting with the [] brackets
   157  		return fmt.Sprintf("[%s]", ip.String()), nil
   158  	} else {
   159  		return "", err
   160  	}
   161  }
   162  
   163  func ipv6EndianTranslation(s string) string {
   164  	var nativeEndianness binary.ByteOrder
   165  
   166  	buf := [2]byte{}
   167  	*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
   168  
   169  	switch buf {
   170  	case [2]byte{0xCD, 0xAB}:
   171  		nativeEndianness = binary.LittleEndian
   172  	case [2]byte{0xAB, 0xCD}:
   173  		nativeEndianness = binary.BigEndian
   174  	default:
   175  		panic("neither little nor big endian detected...")
   176  	}
   177  
   178  	if nativeEndianness == binary.BigEndian {
   179  		return s
   180  	}
   181  
   182  	if len(s) != 32 {
   183  		// not an IPv6 address in hex format
   184  		return ""
   185  	}
   186  
   187  	// read 8 bytes at a time and little-to-big byte swap
   188  	// Ex: fe80:0000:0000:0000:5578:afa9:4caf:27a1 becomes
   189  	//     0000:80fe:0000:0000:a9af:7855:a127:af4c
   190  	swappedBytes := make([]byte, len(s))
   191  	for i := 0; i < len(s); i += 8 {
   192  		swappedBytes[i] = s[i+6]
   193  		swappedBytes[i+1] = s[i+7]
   194  		swappedBytes[i+2] = s[i+4]
   195  		swappedBytes[i+3] = s[i+5]
   196  
   197  		swappedBytes[i+4] = s[i+2]
   198  		swappedBytes[i+5] = s[i+3]
   199  		swappedBytes[i+6] = s[i+0]
   200  		swappedBytes[i+7] = s[i+1]
   201  	}
   202  
   203  	return string(swappedBytes)
   204  }
   205  
   206  func (p *mqlPorts) users() (map[int64]*mqlUser, error) {
   207  	obj, err := CreateResource(p.MqlRuntime, "users", map[string]*llx.RawData{})
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	users := obj.(*mqlUsers)
   212  
   213  	err = users.refreshCache(nil)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	return users.usersByID, nil
   219  }
   220  
   221  func (p *mqlPorts) processesBySocket() (map[int64]*mqlProcess, error) {
   222  	p.lock.Lock()
   223  	defer p.lock.Unlock()
   224  
   225  	if p.processes2ports.Error != nil {
   226  		return nil, p.processes2ports.Error
   227  	}
   228  	if p.processes2ports.State&plugin.StateIsSet != 0 {
   229  		return p.processes2ports.Data, nil
   230  	}
   231  
   232  	// Prerequisites: processes
   233  	obj, err := CreateResource(p.MqlRuntime, "processes", map[string]*llx.RawData{})
   234  	if err != nil {
   235  		p.processes2ports = plugin.TValue[map[int64]*mqlProcess]{
   236  			State: plugin.StateIsSet,
   237  			Error: err,
   238  		}
   239  		return nil, err
   240  	}
   241  	processes := obj.(*mqlProcesses)
   242  
   243  	err = processes.refreshCache(nil)
   244  	if err != nil {
   245  		p.processes2ports = plugin.TValue[map[int64]*mqlProcess]{
   246  			State: plugin.StateIsSet,
   247  			Error: err,
   248  		}
   249  		return nil, err
   250  	}
   251  
   252  	conn := p.MqlRuntime.Connection.(shared.Connection)
   253  	res := map[int64]*mqlProcess{}
   254  	if len(res) == 0 {
   255  		c, err := conn.RunCommand("find /proc -maxdepth 4 -path '/proc/*/fd/*' -exec ls -n {} \\;")
   256  		if err != nil {
   257  			p.processes2ports = plugin.TValue[map[int64]*mqlProcess]{
   258  				State: plugin.StateIsSet,
   259  				Error: errors.New("processes> could not run command: " + err.Error()),
   260  			}
   261  			return nil, p.processes2ports.Error
   262  		}
   263  
   264  		processesBySocket := map[int64]*mqlProcess{}
   265  		scanner := bufio.NewScanner(c.Stdout)
   266  		for scanner.Scan() {
   267  			line := scanner.Text()
   268  			pid, inode, err := parseLinuxFindLine(line)
   269  			if err != nil || (pid == 0 && inode == 0) {
   270  				continue
   271  			}
   272  
   273  			processesBySocket[inode] = processes.ByPID[pid]
   274  		}
   275  		processes.BySocketID = processesBySocket
   276  		res = processesBySocket
   277  	}
   278  
   279  	p.processes2ports = plugin.TValue[map[int64]*mqlProcess]{
   280  		Data:  res,
   281  		State: plugin.StateIsSet,
   282  	}
   283  	return res, err
   284  }
   285  
   286  func parseLinuxFindLine(line string) (int64, int64, error) {
   287  	if strings.HasSuffix(line, "Permission denied") || strings.HasSuffix(line, "No such file or directory") {
   288  		return 0, 0, nil
   289  	}
   290  
   291  	m := reFindSockets.FindStringSubmatch(line)
   292  	if len(m) == 0 {
   293  		return 0, 0, nil
   294  	}
   295  
   296  	pid, err := strconv.ParseInt(m[1], 10, 64)
   297  	if err != nil {
   298  		log.Error().Err(err).Msg("cannot parse unix pid " + m[1])
   299  		return 0, 0, err
   300  	}
   301  
   302  	inode, err := strconv.ParseInt(m[2], 10, 64)
   303  	if err != nil {
   304  		log.Error().Err(err).Msg("cannot parse socket inode " + m[2])
   305  		return 0, 0, err
   306  	}
   307  
   308  	return pid, inode, nil
   309  }
   310  
   311  // See:
   312  // - socket/address parsing: https://wiki.christophchamp.com/index.php?title=Unix_sockets
   313  func (p *mqlPorts) parseProcNet(path string, protocol string, users map[int64]*mqlUser) ([]interface{}, error) {
   314  	conn := p.MqlRuntime.Connection.(shared.Connection)
   315  	fs := conn.FileSystem()
   316  	stat, err := fs.Stat(path)
   317  	if err != nil {
   318  		return nil, errors.New("cannot access stat for " + path)
   319  	}
   320  	if stat.IsDir() {
   321  		return nil, errors.New("something is wrong, looks like " + path + " is a folder")
   322  	}
   323  
   324  	fi, err := fs.Open(path)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	defer fi.Close()
   329  
   330  	var res []interface{}
   331  	scanner := bufio.NewScanner(fi)
   332  	for scanner.Scan() {
   333  		line := scanner.Text()
   334  
   335  		port, err := parseProcNetLine(line)
   336  		if err != nil {
   337  			return nil, fmt.Errorf("failed to parse proc net line: %v", err)
   338  		}
   339  		if port == nil {
   340  			continue
   341  		}
   342  
   343  		obj, err := CreateResource(p.MqlRuntime, "port", map[string]*llx.RawData{
   344  			"protocol":      llx.StringData(protocol),
   345  			"port":          llx.IntData(port.Port),
   346  			"address":       llx.StringData(port.Address),
   347  			"user":          llx.ResourceData(users[port.Uid], "user"),
   348  			"state":         llx.StringData(port.State),
   349  			"remoteAddress": llx.StringData(port.RemoteAddress),
   350  			"remotePort":    llx.IntData(port.RemotePort),
   351  		})
   352  		if err != nil {
   353  			return nil, err
   354  		}
   355  
   356  		po := obj.(*mqlPort)
   357  		po.inode = port.Inode
   358  
   359  		res = append(res, obj)
   360  	}
   361  
   362  	return res, nil
   363  }
   364  
   365  type procNetPort struct {
   366  	Address       string
   367  	Port          int64
   368  	RemoteAddress string
   369  	RemotePort    int64
   370  	State         string
   371  	Uid           int64
   372  	Inode         int64
   373  }
   374  
   375  func parseProcNetLine(line string) (*procNetPort, error) {
   376  	m := reLinuxProcNet.FindStringSubmatch(line)
   377  	port := &procNetPort{}
   378  	if len(m) == 0 {
   379  		return nil, nil
   380  	}
   381  
   382  	var address string
   383  	var err error
   384  	if len(m[1]) > 8 {
   385  		address, err = hex2ipv6(m[1])
   386  	} else {
   387  		address, err = hex2ipv4(m[1])
   388  	}
   389  	if err != nil {
   390  		return nil, errors.New("failed to parse port address: " + m[1])
   391  	}
   392  	port.Address = address
   393  
   394  	localPort, err := strconv.ParseUint(m[2], 16, 64)
   395  	if err != nil {
   396  		return nil, errors.New("failed to parse port number: " + m[2])
   397  	}
   398  	port.Port = int64(localPort)
   399  
   400  	var remoteAddress string
   401  	if len(m[1]) > 8 {
   402  		remoteAddress, err = hex2ipv6(m[3])
   403  	} else {
   404  		remoteAddress, err = hex2ipv4(m[3])
   405  	}
   406  	if err != nil {
   407  		return nil, errors.New("failed to parse port address: " + m[3])
   408  	}
   409  	port.RemoteAddress = remoteAddress
   410  
   411  	remotePort, err := strconv.ParseUint(m[4], 16, 64)
   412  	if err != nil {
   413  		return nil, errors.New("failed to parse port number: " + m[4])
   414  	}
   415  	port.RemotePort = int64(remotePort)
   416  
   417  	stateNum, err := strconv.ParseInt(m[5], 16, 64)
   418  	if err != nil {
   419  		return nil, errors.New("failed to parse state number: " + m[5])
   420  	}
   421  	state, ok := TCP_STATES[stateNum]
   422  	if !ok {
   423  		state = "unknown"
   424  	}
   425  	port.State = state
   426  
   427  	uid, err := strconv.ParseUint(m[6], 10, 64)
   428  	if err != nil {
   429  		return nil, errors.New("failed to parse port UID: " + m[6])
   430  	}
   431  	port.Uid = int64(uid)
   432  
   433  	inode, err := strconv.ParseUint(m[7], 10, 64)
   434  	if err != nil {
   435  		return nil, errors.New("failed to parse port Inode: " + m[7])
   436  	}
   437  	port.Inode = int64(inode)
   438  
   439  	return port, nil
   440  }
   441  
   442  func (p *mqlPorts) listLinux() ([]interface{}, error) {
   443  	users, err := p.users()
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  
   448  	var ports []interface{}
   449  	tcpPorts, err := p.parseProcNet("/proc/net/tcp", "tcp4", users)
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  	ports = append(ports, tcpPorts...)
   454  
   455  	udpPorts, err := p.parseProcNet("/proc/net/udp", "udp4", users)
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  	ports = append(ports, udpPorts...)
   460  
   461  	tcpPortsV6, err := p.parseProcNet("/proc/net/tcp6", "tcp6", users)
   462  	if err != nil {
   463  		return nil, err
   464  	}
   465  	ports = append(ports, tcpPortsV6...)
   466  
   467  	udpPortsV6, err := p.parseProcNet("/proc/net/udp6", "udp6", users)
   468  	if err != nil {
   469  		return nil, err
   470  	}
   471  	ports = append(ports, udpPortsV6...)
   472  
   473  	return ports, nil
   474  }
   475  
   476  func (p *mqlPorts) processesByPid() (map[int64]*mqlProcess, error) {
   477  	// Prerequisites: processes
   478  	obj, err := CreateResource(p.MqlRuntime, "processes", map[string]*llx.RawData{})
   479  	if err != nil {
   480  		return nil, err
   481  	}
   482  	processes := obj.(*mqlProcesses)
   483  
   484  	err = processes.refreshCache(nil)
   485  	if err != nil {
   486  		return nil, err
   487  	}
   488  
   489  	return processes.ByPID, nil
   490  }
   491  
   492  // Windows Implementation
   493  
   494  func (p *mqlPorts) listWindows() ([]interface{}, error) {
   495  	processes, err := p.processesByPid()
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  
   500  	conn := p.MqlRuntime.Connection.(shared.Connection)
   501  	encodedCmd := powershell.Encode("Get-NetTCPConnection | ConvertTo-Json")
   502  	executedCmd, err := conn.RunCommand(encodedCmd)
   503  	if err != nil {
   504  		return nil, err
   505  	}
   506  
   507  	list, err := p.parseWindowsPorts(executedCmd.Stdout, processes)
   508  	if err != nil {
   509  		return nil, err
   510  	}
   511  
   512  	return list, nil
   513  }
   514  
   515  func (p *mqlPorts) parseWindowsPorts(r io.Reader, processes map[int64]*mqlProcess) ([]interface{}, error) {
   516  	portList, err := ports.ParseWindowsNetTCPConnections(r)
   517  	if err != nil {
   518  		return nil, err
   519  	}
   520  
   521  	var res []interface{}
   522  	for i := range portList {
   523  		port := portList[i]
   524  
   525  		var state string
   526  		switch port.State {
   527  		case ports.Listen:
   528  			state = TCP_STATES[10]
   529  		case ports.Closed:
   530  			state = TCP_STATES[7]
   531  		case ports.SynSent:
   532  			state = TCP_STATES[2]
   533  		case ports.SynReceived:
   534  			state = TCP_STATES[3]
   535  		case ports.Established:
   536  			state = TCP_STATES[1]
   537  		case ports.FinWait1:
   538  			state = TCP_STATES[4]
   539  		case ports.FinWait2:
   540  			state = TCP_STATES[5]
   541  		case ports.CloseWait:
   542  			state = TCP_STATES[8]
   543  		case ports.Closing:
   544  			state = TCP_STATES[11]
   545  		case ports.LastAck:
   546  			state = TCP_STATES[9]
   547  		case ports.TimeWait:
   548  			state = TCP_STATES[6]
   549  		case ports.DeleteTCB:
   550  			state = "deletetcb"
   551  		case ports.Bound:
   552  			state = "bound"
   553  		}
   554  
   555  		process := processes[port.OwningProcess]
   556  
   557  		protocol := "tcp4"
   558  		if strings.Contains(port.LocalAddress, ":") {
   559  			protocol = "tcp6"
   560  		}
   561  
   562  		obj, err := CreateResource(p.MqlRuntime, "port", map[string]*llx.RawData{
   563  			"protocol":      llx.StringData(protocol),
   564  			"port":          llx.IntData(port.LocalPort),
   565  			"address":       llx.StringData(port.LocalAddress),
   566  			"user":          llx.ResourceData(nil, "user"),
   567  			"process":       llx.ResourceData(process, "process"),
   568  			"state":         llx.StringData(state),
   569  			"remoteAddress": llx.StringData(port.RemoteAddress),
   570  			"remotePort":    llx.IntData(port.RemotePort),
   571  		})
   572  		if err != nil {
   573  			log.Error().Err(err).Send()
   574  			return nil, err
   575  		}
   576  
   577  		res = append(res, obj)
   578  	}
   579  	return res, nil
   580  }
   581  
   582  // macOS Implementation
   583  
   584  // listMacos reads the lsof information of all open files that are tcp sockets
   585  func (p *mqlPorts) listMacos() ([]interface{}, error) {
   586  	users, err := p.users()
   587  	if err != nil {
   588  		return nil, err
   589  	}
   590  
   591  	processes, err := p.processesByPid()
   592  	if err != nil {
   593  		return nil, err
   594  	}
   595  
   596  	conn := p.MqlRuntime.Connection.(shared.Connection)
   597  	executedCmd, err := conn.RunCommand("lsof -nP -i -F")
   598  	if err != nil {
   599  		return nil, err
   600  	}
   601  
   602  	lsofProcesses, err := lsof.Parse(executedCmd.Stdout)
   603  	if err != nil {
   604  		return nil, err
   605  	}
   606  
   607  	// iterating over all processes to find the once that have network file descriptors
   608  	var res []interface{}
   609  	for i := range lsofProcesses {
   610  		process := lsofProcesses[i]
   611  		for j := range process.FileDescriptors {
   612  			fd := process.FileDescriptors[j]
   613  			if fd.Type != lsof.FileTypeIPv4 && fd.Type != lsof.FileTypeIPv6 {
   614  				continue
   615  			}
   616  
   617  			uid, err := strconv.Atoi(process.UID)
   618  			if err != nil {
   619  				return nil, err
   620  			}
   621  			user := users[int64(uid)]
   622  
   623  			pid, err := strconv.Atoi(process.PID)
   624  			if err != nil {
   625  				return nil, err
   626  			}
   627  			mqlProcess := processes[int64(pid)]
   628  
   629  			protocol := strings.ToLower(fd.Protocol)
   630  			if fd.Type == lsof.FileTypeIPv6 {
   631  				protocol = protocol + "6"
   632  			} else {
   633  				protocol = protocol + "4"
   634  			}
   635  
   636  			localAddress, localPort, remoteAddress, remotePort, err := fd.NetworkFile()
   637  			if err != nil {
   638  				return nil, err
   639  			}
   640  			// lsof presents a process listening on any ipv6 address as listening on "*"
   641  			// change this to a more ipv6-friendly formatting
   642  			if protocol == "ipv6" && strings.HasPrefix(localAddress, "*") {
   643  				localAddress = strings.Replace(localAddress, "*", "[::]", 1)
   644  			}
   645  
   646  			state, ok := TCP_STATES[fd.TcpState()]
   647  			if !ok {
   648  				state = "unknown"
   649  			}
   650  
   651  			obj, err := CreateResource(p.MqlRuntime, "port", map[string]*llx.RawData{
   652  				"protocol":      llx.StringData(protocol),
   653  				"port":          llx.IntData(localPort),
   654  				"address":       llx.StringData(localAddress),
   655  				"user":          llx.ResourceData(user, "user"),
   656  				"process":       llx.ResourceData(mqlProcess, "process"),
   657  				"state":         llx.StringData(state),
   658  				"remoteAddress": llx.StringData(remoteAddress),
   659  				"remotePort":    llx.IntData(remotePort),
   660  			})
   661  			if err != nil {
   662  				log.Error().Err(err).Send()
   663  				return nil, err
   664  			}
   665  
   666  			res = append(res, obj)
   667  		}
   668  	}
   669  
   670  	return res, nil
   671  }
   672  
   673  type mqlPortInternal struct {
   674  	inode int64
   675  }
   676  
   677  func (s *mqlPort) id() (string, error) {
   678  	return fmt.Sprintf("port: %s/%s:%d/%s:%d/%s",
   679  		s.Protocol.Data, s.Address.Data, s.Port.Data,
   680  		s.RemoteAddress.Data, s.RemotePort.Data, s.State.Data), nil
   681  }
   682  
   683  func (s *mqlPort) tls(address string, port int64, proto string) (plugin.Resource, error) {
   684  	if address == "" || address == "0.0.0.0" {
   685  		address = "127.0.0.1"
   686  	}
   687  
   688  	socket, err := s.MqlRuntime.CreateSharedResource("socket", map[string]*llx.RawData{
   689  		"protocol": llx.StringData(proto),
   690  		"port":     llx.IntData(port),
   691  		"address":  llx.StringData(address),
   692  	})
   693  	if err != nil {
   694  		return nil, err
   695  	}
   696  
   697  	return s.MqlRuntime.CreateSharedResource("tls", map[string]*llx.RawData{
   698  		"socket":     llx.ResourceData(socket, "socket"),
   699  		"domainName": llx.StringData(""),
   700  	})
   701  }
   702  
   703  func (s *mqlPort) process() (*mqlProcess, error) {
   704  	// At this point everything except for linux should have their port identified.
   705  	// For linux we need to scour the /proc system, which takes a long time.
   706  	// TODO: massively speed this up on linux with more approach.
   707  	conn := s.MqlRuntime.Connection.(shared.Connection)
   708  	pf := conn.Asset().Platform
   709  	if !pf.IsFamily("linux") {
   710  		return nil, errors.New("unable to detect process for this port")
   711  	}
   712  
   713  	obj, err := CreateResource(s.MqlRuntime, "ports", map[string]*llx.RawData{})
   714  	if err != nil {
   715  		return nil, err
   716  	}
   717  	ports := obj.(*mqlPorts)
   718  
   719  	// TODO: refresh on the fly, eg when loading this from a recording
   720  	if s.inode == 0 {
   721  		return nil, errors.New("no iNode found for this port and cannot yet refresh it")
   722  	}
   723  
   724  	procs, err := ports.processesBySocket()
   725  	if err != nil {
   726  		return nil, err
   727  	}
   728  	proc := procs[s.inode]
   729  	if proc == nil {
   730  		s.Process = plugin.TValue[*mqlProcess]{State: plugin.StateIsSet | plugin.StateIsNull}
   731  	}
   732  	return proc, nil
   733  }