pkg/tools/ssl/node.go (178 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 ssl
import (
"fmt"
"os/exec"
"regexp"
"strings"
"github.com/apache/skywalking-rover/pkg/tools/btf"
"github.com/apache/skywalking-rover/pkg/tools/profiling"
"github.com/apache/skywalking-rover/pkg/tools/version"
"github.com/cilium/ebpf"
)
var (
nodeVersionRegex = regexp.MustCompile(`^node\.js/v(?P<Major>\d+)\.(?P<Minor>\d+)\.(?P<Patch>\d+)$`)
)
type NodeTLSSymbolAddress struct {
TLSWrapStreamListenerOffset uint32
StreamListenerStreamOffset uint32
StreamBaseStreamResourceOffset uint32
LibuvStreamWrapStreamBaseOffset uint32
LibuvStreamWrapStreamOffset uint32
UVStreamSIOWatcherOffset uint32
UVIOSFDOffset uint32
}
func (r *Register) Node(sslSymbolOffsetsMap, nodeTLSSymbolOffsetsMap *ebpf.Map,
sslWrite, sslWriteRet, sslRead, sslReadRet,
sslNew, tlsWrap, tlsWrapRet *ebpf.Program) {
r.addHandler("Node", func() (bool, error) {
libSSLModule, nodeModule, openSSLAttach, err := r.findNodeTLSModules()
if err != nil {
return false, err
}
if libSSLModule == nil || nodeModule == nil {
return false, nil
}
v, err := r.getNodeVersion(nodeModule.Path)
if err != nil {
return false, err
}
log.Debugf("read the nodejs version, pid: %d, version: %s", r.pid, v)
// openSSL symbol offsets
if sslSymbolOffsetsMap != nil {
config, err := r.buildOpenSSLSymAddrConfig(libSSLModule.Path)
if err != nil {
return false, err
}
if err := sslSymbolOffsetsMap.Put(uint32(r.pid), config); err != nil {
return false, err
}
}
if nodeTLSSymbolOffsetsMap != nil {
config, err := r.findNodeTLSAddrConfig(v)
if err != nil {
return false, err
}
// setting the locations
if err := nodeTLSSymbolOffsetsMap.Put(uint32(r.pid), config); err != nil {
return false, fmt.Errorf("setting the node TLS location failure, pid: %d, error: %v", r.pid, err)
}
}
libSSLLinker := r.linker.OpenUProbeExeFile(libSSLModule.Path)
if openSSLAttach {
libSSLLinker.AddLink("SSL_write", sslWrite, sslWriteRet)
libSSLLinker.AddLink("SSL_read", sslRead, sslReadRet)
}
if e := r.nodeTLSRegisterProbes(v, libSSLLinker, nodeModule, sslNew, tlsWrap, tlsWrapRet); e != nil {
return false, e
}
if e := r.linker.HasError(); e != nil {
return false, e
}
return true, nil
})
}
func (r *Register) getNodeVersion(p string) (*version.Version, error) {
result, err := exec.Command("strings", p).Output()
if err != nil {
return nil, err
}
for _, d := range strings.Split(string(result), "\n") {
versionInfo := nodeVersionRegex.FindStringSubmatch(strings.TrimSpace(d))
if len(versionInfo) != 4 {
continue
}
return version.Read(versionInfo[1], versionInfo[2], versionInfo[3])
}
return nil, fmt.Errorf("nodejs version is not found")
}
var nodeTLSAddrWithVersions = []struct {
v *version.Version
conf *NodeTLSSymbolAddress
}{
{version.Build(10, 19, 0), &NodeTLSSymbolAddress{0x0130, 0x08, 0x00, 0x50, 0x90, 0x88, 0x30}},
{version.Build(12, 3, 1), &NodeTLSSymbolAddress{0x0130, 0x08, 0x00, 0x50, 0x90, 0x88, 0x30}},
{version.Build(12, 16, 2), &NodeTLSSymbolAddress{0x0138, 0x08, 0x00, 0x58, 0x98, 0x88, 0x30}},
{version.Build(13, 0, 0), &NodeTLSSymbolAddress{0x0130, 0x08, 0x00, 0x50, 0x90, 0x88, 0x30}},
{version.Build(13, 2, 0), &NodeTLSSymbolAddress{0x0130, 0x08, 0x00, 0x58, 0x98, 0x88, 0x30}},
{version.Build(13, 10, 1), &NodeTLSSymbolAddress{0x0140, 0x08, 0x00, 0x60, 0xa0, 0x88, 0x30}},
{version.Build(14, 5, 0), &NodeTLSSymbolAddress{0x138, 0x08, 0x00, 0x58, 0x98, 0x88, 0x30}},
{version.Build(15, 0, 0), &NodeTLSSymbolAddress{0x78, 0x08, 0x00, 0x58, 0x98, 0x88, 0x30}},
}
var nodeTLSProbeWithVersions = []struct {
v *version.Version
f func(uprobe *btf.UProbeExeFile, register *Register, nodeModule *profiling.Module, tlsWrap, tlsWrapRet *ebpf.Program)
}{
{version.Build(10, 19, 0), func(uprobe *btf.UProbeExeFile, register *Register, nodeModule *profiling.Module, tlsWrap, tlsWrapRet *ebpf.Program) {
uprobe.AddLinkWithSymbols(register.searchSymbolNames([]*profiling.Module{nodeModule}, strings.HasPrefix, "_ZN4node7TLSWrapC2E"),
tlsWrap, tlsWrapRet)
uprobe.AddLinkWithSymbols(register.searchSymbolNames([]*profiling.Module{nodeModule}, strings.HasPrefix, "_ZN4node7TLSWrap7ClearInE"),
tlsWrap, tlsWrapRet)
uprobe.AddLinkWithSymbols(register.searchSymbolNames([]*profiling.Module{nodeModule}, strings.HasPrefix, "_ZN4node7TLSWrap8ClearOutE"),
tlsWrap, tlsWrapRet)
}},
{version.Build(15, 0, 0), func(uprobe *btf.UProbeExeFile, register *Register, nodeModule *profiling.Module, tlsWrap, tlsWrapRet *ebpf.Program) {
uprobe.AddLinkWithSymbols(register.searchSymbolNames([]*profiling.Module{nodeModule}, strings.HasPrefix, "_ZN4node6crypto7TLSWrapC2E"),
tlsWrap, tlsWrapRet)
uprobe.AddLinkWithSymbols(register.searchSymbolNames([]*profiling.Module{nodeModule}, strings.HasPrefix, "_ZN4node6crypto7TLSWrap7ClearInE"),
tlsWrap, tlsWrapRet)
uprobe.AddLinkWithSymbols(register.searchSymbolNames([]*profiling.Module{nodeModule}, strings.HasPrefix, "_ZN4node6crypto7TLSWrap8ClearOutE"),
tlsWrap, tlsWrapRet)
}},
}
func (r *Register) findNodeTLSAddrConfig(v *version.Version) (*NodeTLSSymbolAddress, error) {
var lastest *NodeTLSSymbolAddress
for _, c := range nodeTLSAddrWithVersions {
if v.GreaterOrEquals(c.v) {
lastest = c.conf
}
}
if lastest != nil {
return lastest, nil
}
return nil, fmt.Errorf("could not support version: %s", v)
}
func (r *Register) findNodeTLSModules() (libSSLModule, nodeModule *profiling.Module, openSSLAttach bool, err error) {
moduleName1, moduleName2, libsslName := "/nodejs", "/node", "libssl.so"
processModules, err := r.findModules(moduleName1, moduleName2, libsslName)
if err != nil {
return nil, nil, false, err
}
nodeModule = processModules[moduleName1]
libSSLModule = processModules[libsslName]
if nodeModule == nil {
nodeModule = processModules[moduleName2]
}
if nodeModule == nil {
return nil, nil, false, nil
}
if libSSLModule == nil {
if r.searchSymbolInModules([]*profiling.Module{nodeModule}, func(a, b string) bool {
return a == b
}, "SSL_read") == nil || r.searchSymbolInModules([]*profiling.Module{nodeModule}, func(a, b string) bool {
return a == b
}, "SSL_write") == nil {
return nil, nil, false, nil
}
libSSLModule = nodeModule
openSSLAttach = true
}
return
}
func (r *Register) nodeTLSRegisterProbes(v *version.Version, libSSLLinker *btf.UProbeExeFile, nodeModule *profiling.Module,
sslNew, tlsWrap, tlsWrapRet *ebpf.Program) error {
if sslNew != nil {
libSSLLinker.AddLinkWithType("SSL_new", false, sslNew)
}
if tlsWrap != nil && tlsWrapRet != nil {
var probeFunc func(uprobe *btf.UProbeExeFile, register *Register, nodeModule *profiling.Module, tlsWrap, tlsWrapRet *ebpf.Program)
for _, c := range nodeTLSProbeWithVersions {
if v.GreaterOrEquals(c.v) {
probeFunc = c.f
}
}
if probeFunc == nil {
return fmt.Errorf("the version is not support to attach TLSWrap relate probes: %v, pid: %d", v, r.pid)
}
file := r.linker.OpenUProbeExeFile(nodeModule.Path)
probeFunc(file, r, nodeModule, tlsWrap, tlsWrapRet)
}
return nil
}