pkg/profiling/task/network/analyze/base/tcpresolver.go (121 lines of code) (raw):
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package base
import (
"encoding/binary"
"fmt"
"net"
"os"
"regexp"
"strconv"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/apache/skywalking-rover/pkg/tools/host"
)
var (
notBlankRegex = regexp.MustCompile(`\s+`)
ipv4StrLen = 8
ipv6StrLen = 32
)
type SocketPair struct {
SrcIP string
SrcPort uint16
DestIP string
DestPort uint16
}
func ParseSocket(pid, sockfd uint32) (*SocketPair, error) {
link := fmt.Sprintf("/proc/%d/fd/%d", pid, sockfd)
dest, err := os.Readlink(host.GetFileInHost(link))
if err != nil {
return nil, fmt.Errorf("could not read the socket real link: %v", err)
}
if !strings.HasPrefix(dest, "socket:[") {
return nil, fmt.Errorf("current file is not socket: %s", dest)
}
inode := strings.TrimSuffix(strings.TrimPrefix(dest, "socket:["), "]")
var s *SocketPair
s, err = foundAddressByFile(s, err, fmt.Sprintf(host.GetFileInHost("/proc/%d/net/tcp"), pid), inode)
s, err = foundAddressByFile(s, err, fmt.Sprintf(host.GetFileInHost("/proc/%d/net/tcp6"), pid), inode)
return s, err
}
func foundAddressByFile(s *SocketPair, exitedError error, filename, inode string) (*SocketPair, error) {
if s != nil {
return s, nil
}
data, err := os.ReadFile(filename)
if err != nil {
return nil, multierror.Append(exitedError, err)
}
lines := strings.Split(string(data), "\n")
for _, line := range lines[1 : len(lines)-1] {
source := notBlankRegex.Split(strings.TrimSpace(line), -1)
if len(source) < 10 {
continue
}
tcpINode := source[9]
if tcpINode != inode {
continue
}
// ignore local listenning records
destIP, destPort, err := parseAddr(source[2])
if err != nil {
return nil, multierror.Append(exitedError, fmt.Errorf("parsing address in file failure: %s, error: %s", filename, err))
}
// source ip and port
ip, port, err := parseAddr(source[1])
if err != nil {
return nil, multierror.Append(exitedError, fmt.Errorf("parsing address in file failure: %s, error: %s", filename, err))
}
return &SocketPair{
SrcIP: ip,
SrcPort: port,
DestIP: destIP,
DestPort: destPort,
}, nil
}
return nil, multierror.Append(exitedError, fmt.Errorf("could not found the socket: %s, in file: %s", inode, filename))
}
func parseAddr(str string) (ipAddr string, port uint16, err error) {
fields := strings.Split(str, ":")
if len(fields) < 2 {
return "", 0, fmt.Errorf("netstat: not enough fields: %v", fields)
}
var ip net.IP
switch len(fields[0]) {
case ipv4StrLen:
ip, err = parseIPv4(fields[0])
case ipv6StrLen:
ip, err = parseIPv6(fields[0])
default:
err = fmt.Errorf("netstat: bad formatted string: %v", fields[0])
}
if err != nil {
return "", 0, err
}
v, err := strconv.ParseUint(fields[1], 16, 16)
if err != nil {
return "", 0, err
}
return ip.String(), uint16(v), nil
}
func parseIPv4(s string) (net.IP, error) {
v, err := strconv.ParseUint(s, 16, 32)
if err != nil {
return nil, err
}
ip := make(net.IP, net.IPv4len)
binary.LittleEndian.PutUint32(ip, uint32(v))
return ip, nil
}
func parseIPv6(s string) (net.IP, error) {
ip := make(net.IP, net.IPv6len)
const grpLen = 4
i, j := 0, 4
for s != "" {
grp := s[0:8]
u, err := strconv.ParseUint(grp, 16, 32)
binary.LittleEndian.PutUint32(ip[i:j], uint32(u))
if err != nil {
return nil, err
}
i, j = i+grpLen, j+grpLen
s = s[8:]
}
return ip, nil
}