Skip to content

Commit aaefa76

Browse files
authored
Merge pull request #325 from DataDog/vickenty/dho
Pass origin information to the forwarder
2 parents 39cb47b + 8d24281 commit aaefa76

8 files changed

Lines changed: 894 additions & 63 deletions

File tree

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,14 @@ jobs:
6161
run: mvn clean install
6262
- name: Test with latest jnr dependencies
6363
run: mvn test -P jnr-latest
64+
65+
check-vendored:
66+
name: Check vendored code
67+
runs-on: ubuntu-latest
68+
steps:
69+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
70+
- name: CgroupReader is in sync
71+
run: >
72+
diff
73+
<(grep -v ^package src/main/java/com/timgroup/statsd/CgroupReader.java)
74+
<(grep -v ^package dogstatsd-http/core/src/main/java/com/datadoghq/dogstatsd/http/CgroupReader.java)
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package com.datadoghq.dogstatsd.http;
2+
3+
import java.io.IOException;
4+
import java.nio.file.Files;
5+
import java.nio.file.Path;
6+
import java.nio.file.Paths;
7+
import java.util.Arrays;
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
13+
14+
/**
15+
* A reader class that retrieves the current container ID or the cgroup controller inode parsed from
16+
* the cgroup file.
17+
*/
18+
class CgroupReader {
19+
private static final Path CGROUP_PATH = Paths.get("/proc/self/cgroup");
20+
private static final String UUID_SOURCE = "[0-9a-f]{8}(?:[_-][0-9a-f]{4}){3}[_-][0-9a-f]{12}";
21+
private static final String CONTAINER_SOURCE = "[0-9a-f]{64}";
22+
private static final String TASK_SOURCE = "[0-9a-f]{32}-\\d+";
23+
private static final Pattern LINE_RE =
24+
Pattern.compile("^\\d+:[^:]*:(.+)$", Pattern.MULTILINE | Pattern.UNIX_LINES);
25+
private static final Pattern CONTAINER_RE =
26+
Pattern.compile(
27+
"("
28+
+ UUID_SOURCE
29+
+ "|"
30+
+ CONTAINER_SOURCE
31+
+ "|"
32+
+ TASK_SOURCE
33+
+ ")(?:.scope)?$");
34+
35+
/** DEFAULT_CGROUP_MOUNT_PATH is the default cgroup mount path. */
36+
private static final Path DEFAULT_CGROUP_MOUNT_PATH = Paths.get("/sys/fs/cgroup");
37+
38+
/** CGROUP_NS_PATH is the path to the cgroup namespace file. */
39+
private static final Path CGROUP_NS_PATH = Paths.get("/proc/self/ns/cgroup");
40+
41+
/**
42+
* CGROUPV1_BASE_CONTROLLER is the controller used to identify the container-id in cgroup v1
43+
* (memory).
44+
*/
45+
private static final String CGROUPV1_BASE_CONTROLLER = "memory";
46+
47+
/**
48+
* CGROUPV2_BASE_CONTROLLER is the controller used to identify the container-id in cgroup v2.
49+
*/
50+
private static final String CGROUPV2_BASE_CONTROLLER = "";
51+
52+
/** HOST_CGROUP_NAMESPACE_INODE is the inode of the host cgroup namespace. */
53+
private static final long HOST_CGROUP_NAMESPACE_INODE = 0xEFFFFFFBL;
54+
55+
private final Path MOUNTINFO_PATH = Paths.get("/proc/self/mountinfo");
56+
57+
private final Pattern MOUNTINFO_RE =
58+
Pattern.compile(
59+
".*/([^\\s/]+)/(([0-9a-f]{64})|([0-9a-f]{32}-\\d+)|([0-9a-f]{8}(-[0-9a-f]{4}){4})$)/[\\S]*hostname");
60+
61+
interface Fs {
62+
String getContents(Path path) throws IOException;
63+
64+
long getInode(Path path) throws IOException;
65+
}
66+
67+
static class FilesFs implements Fs {
68+
@Override
69+
public String getContents(Path path) throws IOException {
70+
return new String(Files.readAllBytes(path));
71+
}
72+
73+
@Override
74+
public long getInode(Path path) throws IOException {
75+
return (long) Files.getAttribute(path, "unix:ino");
76+
}
77+
}
78+
79+
private final Fs fs;
80+
81+
CgroupReader() {
82+
this(new FilesFs());
83+
}
84+
85+
CgroupReader(Fs fs) {
86+
super();
87+
this.fs = fs;
88+
}
89+
90+
/**
91+
* Returns the container ID if available or the cgroup controller inode.
92+
*
93+
* @throws IOException if /proc/self/cgroup is readable and still an I/O error occurs reading
94+
* from the stream.
95+
*/
96+
public String getContainerID() {
97+
String containerID = null;
98+
99+
String cgroupContent = null;
100+
try {
101+
cgroupContent = fs.getContents(CGROUP_PATH);
102+
} catch (IOException ex) {
103+
// ignored
104+
}
105+
106+
if (!isEmpty(cgroupContent)) {
107+
containerID = parseSelfCgroup(cgroupContent);
108+
}
109+
110+
if (!isEmpty(containerID)) {
111+
return containerID;
112+
}
113+
114+
containerID = trySelfMountInfo();
115+
if (!isEmpty(containerID)) {
116+
return containerID;
117+
}
118+
119+
/*
120+
* If the container ID is not available it means that the application is either
121+
* not running in a container or running is private cgroup namespace, we
122+
* fallback to the cgroup controller inode. The agent (7.51+) will use it to get
123+
* the container ID.
124+
* In Host cgroup namespace, the container ID should be found. If it is not
125+
* found, it means that the application is running on a host/vm.
126+
*
127+
*/
128+
if (!isEmpty(cgroupContent) && !isHostCgroupNamespace(CGROUP_NS_PATH)) {
129+
containerID = getCgroupInode(DEFAULT_CGROUP_MOUNT_PATH, cgroupContent);
130+
}
131+
return containerID;
132+
}
133+
134+
/**
135+
* Parses a Cgroup file (=/proc/self/cgroup) content and returns the corresponding container ID.
136+
* It can be found only if the container is running in host cgroup namespace.
137+
*
138+
* @param cgroupsContent Cgroup file content
139+
*/
140+
public static String parseSelfCgroup(final String cgroupsContent) {
141+
final Matcher lines = LINE_RE.matcher(cgroupsContent);
142+
while (lines.find()) {
143+
final String path = lines.group(1);
144+
final Matcher matcher = CONTAINER_RE.matcher(path);
145+
if (matcher.find()) {
146+
return matcher.group(1);
147+
}
148+
}
149+
150+
return null;
151+
}
152+
153+
/**
154+
* Returns true if the host cgroup namespace is used. It looks at the inode of
155+
* `/proc/self/ns/cgroup` and compares it to HOST_CGROUP_NAMESPACE_INODE.
156+
*
157+
* @param path Path to the cgroup namespace file.
158+
*/
159+
private boolean isHostCgroupNamespace(final Path path) {
160+
long hostCgroupInode = inodeForPath(path);
161+
return hostCgroupInode == HOST_CGROUP_NAMESPACE_INODE;
162+
}
163+
164+
/**
165+
* Returns the inode for the given path.
166+
*
167+
* @param path Path to the cgroup namespace file.
168+
*/
169+
private long inodeForPath(final Path path) {
170+
try {
171+
long inode = (long) fs.getInode(path);
172+
return inode;
173+
} catch (Exception e) {
174+
return 0;
175+
}
176+
}
177+
178+
/**
179+
* Returns the cgroup controller inode for the given cgroup mount path and procSelfCgroupPath.
180+
*
181+
* @param cgroupMountPath Path to the cgroup mount point.
182+
* @param cgroupContent String content of the cgroup file.
183+
*/
184+
public String getCgroupInode(final Path cgroupMountPath, final String cgroupContent) {
185+
Map<String, String> cgroupControllersPaths = parseCgroupNodePath(cgroupContent);
186+
if (cgroupControllersPaths == null) {
187+
return null;
188+
}
189+
190+
// Retrieve the cgroup inode from /sys/fs/cgroup+controller+cgroupNodePath
191+
List<String> controllers =
192+
Arrays.asList(CGROUPV1_BASE_CONTROLLER, CGROUPV2_BASE_CONTROLLER);
193+
for (String controller : controllers) {
194+
String cgroupNodePath = cgroupControllersPaths.get(controller);
195+
if (cgroupNodePath == null) {
196+
continue;
197+
}
198+
Path path = Paths.get(cgroupMountPath.toString(), controller, cgroupNodePath);
199+
long inode = inodeForPath(path);
200+
/*
201+
* Inode 0 is not a valid inode. Inode 1 is a bad block inode and inode 2 is the
202+
* root of a filesystem. We can safely ignore them.
203+
*/
204+
if (inode > 2) {
205+
return "in-" + inode;
206+
}
207+
}
208+
209+
return null;
210+
}
211+
212+
/**
213+
* Returns a map of cgroup controllers and their corresponding cgroup path.
214+
*
215+
* @param cgroupContent Cgroup file content.
216+
*/
217+
public Map<String, String> parseCgroupNodePath(final String cgroupContent) {
218+
Map<String, String> res = new HashMap<>();
219+
220+
for (String line : cgroupContent.split("\n")) {
221+
String[] tokens = line.split(":");
222+
if (tokens.length != 3) {
223+
continue;
224+
}
225+
if (CGROUPV1_BASE_CONTROLLER.equals(tokens[1])
226+
|| CGROUPV2_BASE_CONTROLLER.equals(tokens[1])) {
227+
res.put(tokens[1], tokens[2]);
228+
}
229+
}
230+
231+
return res;
232+
}
233+
234+
private static boolean isEmpty(String str) {
235+
return str == null || str.isEmpty();
236+
}
237+
238+
String trySelfMountInfo() {
239+
String mountInfo;
240+
try {
241+
mountInfo = fs.getContents(MOUNTINFO_PATH);
242+
} catch (IOException ex) {
243+
return null;
244+
}
245+
246+
for (String line : mountInfo.split("\n")) {
247+
Matcher matcher = MOUNTINFO_RE.matcher(line);
248+
if (matcher.find()) {
249+
if (!"sandboxes".equals(matcher.group(1))) {
250+
return matcher.group(2);
251+
}
252+
}
253+
}
254+
255+
return null;
256+
}
257+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.datadoghq.dogstatsd.http;
2+
3+
import java.util.Map;
4+
5+
class EnvMap {
6+
private final Map<String, String> env;
7+
8+
EnvMap() {
9+
env = null;
10+
}
11+
12+
EnvMap(Map<String, String> provided) {
13+
env = provided;
14+
}
15+
16+
String get(String name) {
17+
return env != null ? env.get(name) : System.getenv(name);
18+
}
19+
}

0 commit comments

Comments
 (0)