Skip to content

Commit 76e24fe

Browse files
committed
Add tests
1 parent 87f4825 commit 76e24fe

File tree

2 files changed

+164
-8
lines changed

2 files changed

+164
-8
lines changed

internal/otelcontextmapping_linux.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,10 @@ type processContextHeader struct {
4545
}
4646

4747
func CreateOtelProcessContextMapping(data []byte) error {
48-
if existingMappingBytes != nil && publisherPID == os.Getpid() {
49-
// Unmap the previous mapping if it exists
50-
err := unix.Munmap(existingMappingBytes)
51-
if err != nil {
52-
return fmt.Errorf("failed to munmap previous mapping: %w", err)
53-
}
54-
existingMappingBytes = nil
55-
publisherPID = 0
48+
// Clear the previous mapping if it exists
49+
err := RemoveOtelProcessContextMapping()
50+
if err != nil {
51+
return fmt.Errorf("failed to remove previous mapping: %w", err)
5652
}
5753

5854
headerSize := int(unsafe.Sizeof(processContextHeader{}))
@@ -116,3 +112,19 @@ func CreateOtelProcessContextMapping(data []byte) error {
116112
publisherPID = os.Getpid()
117113
return nil
118114
}
115+
116+
func RemoveOtelProcessContextMapping() error {
117+
//Check publisher PID to check that the process has not forked.
118+
//It should not be necessary for Go, but just in case.
119+
if existingMappingBytes == nil || publisherPID != os.Getpid() {
120+
return nil
121+
}
122+
123+
err := unix.Munmap(existingMappingBytes)
124+
if err != nil {
125+
return fmt.Errorf("failed to munmap: %w", err)
126+
}
127+
existingMappingBytes = nil
128+
publisherPID = 0
129+
return nil
130+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2025 Datadog, Inc.
5+
6+
//go:build linux
7+
8+
package internal
9+
10+
import (
11+
"bufio"
12+
"bytes"
13+
"errors"
14+
"fmt"
15+
"io"
16+
"os"
17+
"strconv"
18+
"strings"
19+
"testing"
20+
"unsafe"
21+
22+
"github.com/stretchr/testify/require"
23+
"golang.org/x/sys/unix"
24+
)
25+
26+
func getContextFromMapping(fields []string) []byte {
27+
if fields[1] != "r--p" || fields[4] != "0" || fields[3] != "00:00" {
28+
return nil
29+
}
30+
31+
addrs := strings.SplitN(fields[0], "-", 2)
32+
if len(addrs) < 2 {
33+
return nil
34+
}
35+
vaddr, err := strconv.ParseUint(addrs[0], 16, 64)
36+
if err != nil {
37+
return nil
38+
}
39+
40+
vend, err := strconv.ParseUint(addrs[1], 16, 64)
41+
if err != nil {
42+
return nil
43+
}
44+
45+
length := vend - vaddr
46+
if length != uint64(otelContextMappingSize) {
47+
return nil
48+
}
49+
50+
header := (*processContextHeader)(unsafe.Pointer(uintptr(vaddr)))
51+
if string(header.Signature[:]) != otelContextSignature {
52+
return nil
53+
}
54+
if header.Version != 1 {
55+
return nil
56+
}
57+
58+
payload := make([]byte, header.PayloadSize)
59+
copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(header.PayloadAddr)), header.PayloadSize))
60+
return payload
61+
}
62+
63+
func getContextMapping(mapsFile io.Reader, useMappingNames bool) ([]byte, error) {
64+
minFields := 5
65+
if useMappingNames {
66+
minFields++
67+
}
68+
scanner := bufio.NewScanner(mapsFile)
69+
for scanner.Scan() {
70+
71+
line := scanner.Text()
72+
fields := strings.Fields(line)
73+
if len(fields) < minFields {
74+
continue
75+
}
76+
77+
if (useMappingNames && fields[5] != "[anon:OTEL_CTX]") || (!useMappingNames && fields[5] != "") {
78+
continue
79+
}
80+
81+
payload := getContextFromMapping(fields)
82+
if payload != nil {
83+
return payload, nil
84+
}
85+
86+
if useMappingNames {
87+
// When using mapping names, we can stop after the first match.
88+
break
89+
}
90+
}
91+
return nil, errors.New("no context mapping found")
92+
}
93+
94+
func readProcessLevelContext(useMappingNames bool) ([]byte, error) {
95+
mapsFile, err := os.Open("/proc/self/maps")
96+
if err != nil {
97+
return nil, err
98+
}
99+
defer mapsFile.Close()
100+
101+
return getContextMapping(mapsFile, useMappingNames)
102+
}
103+
104+
func kernelSupportsNamedAnonymousMappings() (bool, error) {
105+
var uname unix.Utsname
106+
if err := unix.Uname(&uname); err != nil {
107+
return false, fmt.Errorf("could not get Kernel Version: %v", err)
108+
}
109+
var major, minor, patch uint32
110+
_, _ = fmt.Fscanf(bytes.NewReader(uname.Release[:]), "%d.%d.%d", &major, &minor, &patch)
111+
112+
return major > 5 || (major == 5 && minor >= 17), nil
113+
}
114+
115+
func TestCreateOtelProcessContextMapping(t *testing.T) {
116+
RemoveOtelProcessContextMapping()
117+
t.Cleanup(func() {
118+
RemoveOtelProcessContextMapping()
119+
})
120+
121+
payload := []byte("hello world")
122+
err := CreateOtelProcessContextMapping(payload)
123+
require.NoError(t, err)
124+
125+
supportsNamedAnonymousMappings, err := kernelSupportsNamedAnonymousMappings()
126+
require.NoError(t, err)
127+
128+
ctx, err := readProcessLevelContext(supportsNamedAnonymousMappings)
129+
require.NoError(t, err)
130+
require.Equal(t, payload, ctx)
131+
}
132+
133+
func TestCreateOtelProcessContextMappingRejectsOversizedPayload(t *testing.T) {
134+
RemoveOtelProcessContextMapping()
135+
t.Cleanup(func() {
136+
RemoveOtelProcessContextMapping()
137+
})
138+
139+
headerSize := int(unsafe.Sizeof(processContextHeader{}))
140+
oversizedPayload := make([]byte, otelContextMappingSize-headerSize+1)
141+
142+
err := CreateOtelProcessContextMapping(oversizedPayload)
143+
require.Error(t, err)
144+
}

0 commit comments

Comments
 (0)