Skip to content

Commit 9509edc

Browse files
committed
v0.1: release first version
0 parents  commit 9509edc

14 files changed

+1170
-0
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/

Diff for: LICENSE

+674
Large diffs are not rendered by default.

Diff for: README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# libnss_docker
2+
*A NSS service for Docker containers resolving*
3+
4+
:warning: *This is a **toy** project and has not been intensely tested! Use at your own risk*
5+
6+
## Dependencies
7+
8+
- `libcurl` (>= 7.80)
9+
- `libcjson` (>= 1.7.15)
10+
11+
*Note that older versions of the dependencies may work, but they have not been tested*
12+
13+
## Install
14+
15+
Requires `meson` and `ninja`.
16+
17+
```bash
18+
meson build && cd build && meson install
19+
```
20+
21+
## Build
22+
23+
Requires `meson` and `ninja`.
24+
25+
```bash
26+
meson --buildtype=debug build && cd build && meson compile
27+
```

Diff for: meson.build

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
project(
2+
'nss-docker', 'c',
3+
version: '0.1',
4+
license: 'GPL-3.0-or-later',
5+
meson_version: '>=0.61.1',
6+
default_options: [
7+
'prefix=/usr',
8+
'buildtype=release',
9+
'warning_level=3',
10+
'werror=true',
11+
'b_lto=true',
12+
'b_lto_mode=thin',
13+
'b_ndebug=if-release',
14+
'strip=true' # only strip on install
15+
],
16+
)
17+
18+
subdir('src')
19+
20+
install_man('nss-docker.8')

Diff for: nss-docker.8

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.TH NSS-DOCKER 8 2022-02-11 "nss-docker 0.1"
2+
3+
.SH NAME
4+
nss-docker, libnss_docker\&.so\&.2 - Hostname resolution for Docker containers
5+
6+
.SH DESCRIPTION
7+
.PP
8+
\fBnss-docker\fP is a plug-in module for the GNU Name Service Switch (NSS) functionality of the glibc providing hostname resolution for the names and UUID of Docker containers.
9+
.PP
10+
To use this module, add "docker" to the line starting with "hosts:" in the file /etc/nssswitch.conf.
11+
12+
.SH EXAMPLE
13+
.PP
14+
Here is an example of /etc/nssswitch.conf with "docker" enabled:
15+
.PP
16+
.RS 4
17+
.EX
18+
passwd: files systemd
19+
group: files [SUCCESS=merge] systemd
20+
shadow: files systemd
21+
gshadow: files systemd
22+
23+
publickey: files
24+
25+
hosts: mymachines mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files myhostname \fBdocker\fP dns
26+
networks: files
27+
28+
protocols: files
29+
services: files
30+
ethers: files
31+
rpc: files
32+
33+
netgroup: files
34+
.EE
35+
36+
It is recommended to place "docker" at the end \fBbut before\fP "dns".
37+
38+
.SH SEE ALSO
39+
.PP
40+
\fBnsswitch.conf\fP(5), \fBgetent\fP(1)
41+
42+
.SH AUTHOR
43+
Kenji 'Nhqml' Gaillac

Diff for: src/container.c

Whitespace-only changes.

Diff for: src/container.h

Whitespace-only changes.

Diff for: src/docker_api.c

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#include <errno.h>
2+
#include <error.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
6+
#include <cjson/cJSON.h>
7+
#include <curl/curl.h>
8+
9+
#include "docker_api.h"
10+
#include "utils.h"
11+
12+
size_t jsonify(void *data, size_t size, size_t nmemb, void *userdata)
13+
{
14+
size_t realsize = size * nmemb;
15+
16+
struct cJSON *json = cJSON_Parse(data);
17+
*(struct cJSON **)userdata = json;
18+
19+
return realsize;
20+
}
21+
22+
char *_inspect_container_url(const char *name)
23+
{
24+
char *url = xmalloc(_DOCKER_API_URL_BASE_SIZE + strlen(name) + 1);
25+
sprintf(url, DOCKER_API_ENDPOINT DOCKER_API_VERSION DOCKER_API_URL_P1 "%s" DOCKER_API_URL_P2, name);
26+
27+
return url;
28+
}
29+
30+
struct container *container_new(const char *name)
31+
{
32+
struct container *container = xmalloc(sizeof(struct container));
33+
34+
size_t name_len = strlen(name);
35+
36+
container->name = xmalloc(name_len + 1);
37+
strcpy(container->name, name);
38+
container->short_uuid = xmalloc(DOCKER_SHORT_UUID_SIZE + 1);
39+
40+
container->addr_list = xcalloc(1, sizeof(char *));
41+
container->n_addr = 0;
42+
43+
return container;
44+
}
45+
46+
struct container *container_set_short_uuid(struct container *container, const char *uuid)
47+
{
48+
strncpy(container->short_uuid, uuid, DOCKER_SHORT_UUID_SIZE);
49+
container->short_uuid[DOCKER_SHORT_UUID_SIZE] = '\0';
50+
51+
return container;
52+
}
53+
54+
struct container *container_add_addr(struct container *container, char *addr)
55+
{
56+
if (addr != NULL)
57+
{
58+
++container->n_addr;
59+
container->addr_list = xreallocarray(container->addr_list, container->n_addr + 1, sizeof(char *));
60+
container->addr_list[container->n_addr - 1] = addr;
61+
container->addr_list[container->n_addr] = NULL;
62+
}
63+
64+
return container;
65+
}
66+
67+
void container_free(struct container *container)
68+
{
69+
if (container != NULL)
70+
{
71+
free(container->name);
72+
free(container->short_uuid);
73+
74+
for (char **addr = container->addr_list; *addr != NULL; ++addr)
75+
free(*addr);
76+
free(container->addr_list);
77+
78+
free(container);
79+
}
80+
}
81+
82+
struct container *inspect_container(const char *name, int af)
83+
{
84+
CURL *curl = curl_easy_init();
85+
if (curl == NULL)
86+
error(1, errno, "failed when trying to retrieve containers");
87+
88+
struct curl_slist *headers = curl_slist_append(NULL, "Accept: application/json");
89+
headers = curl_slist_append(headers, "Content-Type: application/json; charset=utf-8");
90+
91+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
92+
curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, DOCKER_API_SOCK);
93+
94+
char *docker_api_url = _inspect_container_url(name);
95+
curl_easy_setopt(curl, CURLOPT_URL, docker_api_url);
96+
97+
struct cJSON *json;
98+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, jsonify);
99+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&json);
100+
101+
CURLcode res = curl_easy_perform(curl);
102+
if (res != CURLE_OK)
103+
error(1, 0, "failed when trying to retrieve containers: %s", curl_easy_strerror(res));
104+
105+
long http_code = 0;
106+
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
107+
108+
free(docker_api_url);
109+
curl_easy_cleanup(curl);
110+
111+
if (http_code != 200)
112+
return NULL;
113+
114+
struct container *container = NULL;
115+
116+
struct cJSON *container_name = cJSON_GetObjectItemCaseSensitive(json, "Name");
117+
if (!cJSON_IsString(container_name))
118+
return NULL;
119+
120+
container = container_new(container_name->valuestring + 1); // +1 to skip leading '/' in the name
121+
122+
struct cJSON *container_uuid = cJSON_GetObjectItemCaseSensitive(json, "Id");
123+
if (cJSON_IsString(container_uuid))
124+
container_set_short_uuid(container, container_uuid->valuestring);
125+
126+
struct cJSON *container_netsettings = cJSON_GetObjectItemCaseSensitive(json, "NetworkSettings");
127+
if (container_netsettings != NULL)
128+
{
129+
struct cJSON *container_nets = cJSON_GetObjectItemCaseSensitive(container_netsettings, "Networks");
130+
131+
for (int i = 0; i < cJSON_GetArraySize(container_nets); ++i)
132+
{
133+
struct cJSON *container_net = cJSON_GetArrayItem(container_nets, i);
134+
if (container_net != NULL)
135+
{
136+
char *container_addr = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(container_net, "IPAddress"));
137+
138+
if (container_addr != NULL && container_addr[0] != '\0')
139+
container_add_addr(container, parse_addr(container_addr, af));
140+
}
141+
}
142+
}
143+
144+
cJSON_free(json);
145+
146+
if (container->n_addr == 0)
147+
{
148+
container_free(container);
149+
container = NULL;
150+
}
151+
152+
return container;
153+
}

Diff for: src/docker_api.h

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#define DOCKER_API_SOCK "/var/run/docker.sock"
4+
#define DOCKER_API_ENDPOINT "http://localhost/"
5+
#define DOCKER_API_VERSION "v1.41"
6+
7+
#define DOCKER_API_URL_P1 "/containers/"
8+
#define DOCKER_API_URL_P2 "/json"
9+
10+
#define _DOCKER_API_URL_BASE_SIZE 39
11+
12+
#define DOCKER_UUID_SIZE 64
13+
#define DOCKER_SHORT_UUID_SIZE 12
14+
15+
#define DOCKER_LOCAL_DOMAIN ".docker.local"
16+
17+
struct container
18+
{
19+
char *name;
20+
char *short_uuid;
21+
size_t n_addr;
22+
char **addr_list;
23+
};
24+
25+
struct container *inspect_container(const char *name, int af);

Diff for: src/docker_nss.c

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#include <arpa/inet.h>
2+
#include <netdb.h>
3+
#include <nss.h>
4+
#include <string.h>
5+
6+
#include "docker_api.h"
7+
#include "docker_nss.h"
8+
#include "utils.h"
9+
10+
enum nss_status _nss_docker_gethostbyname_r(const char *name,
11+
struct hostent *result,
12+
char *buf, size_t buflen,
13+
int *errnop, int *h_errnop)
14+
{
15+
return _nss_docker_gethostbyname3_r(name, AF_INET, result, buf, buflen, errnop, h_errnop, NULL, NULL);
16+
}
17+
18+
enum nss_status _nss_docker_gethostbyname2_r(const char *name, int af,
19+
struct hostent *result,
20+
char *buf, size_t buflen,
21+
int *errnop, int *h_errnop)
22+
{
23+
return _nss_docker_gethostbyname3_r(name, af, result, buf, buflen, errnop, h_errnop, NULL, NULL);
24+
}
25+
26+
/**
27+
** \brief Returns the name without docker local domain
28+
*/
29+
char *strip_docker_local_domain(const char *name)
30+
{
31+
if (name == NULL)
32+
return NULL;
33+
34+
char *stripped_name = xstrdup(name);
35+
36+
for (char *c = stripped_name; *c != '\0'; ++c)
37+
{
38+
if (*c == '.')
39+
{
40+
if (strcmp(c, DOCKER_LOCAL_DOMAIN) == 0)
41+
{
42+
*c = '\0'; // Terminate the string here
43+
break;
44+
}
45+
}
46+
}
47+
48+
return stripped_name;
49+
}
50+
51+
enum nss_status _nss_docker_gethostbyname3_r(const char *name, int af,
52+
struct hostent *result,
53+
char *buf, size_t buflen,
54+
int *errnop, int *h_errnop,
55+
int32_t *ttlp, char **canonp)
56+
{
57+
/* I don't know for sure what ttlp is, my guess is that it's a pointer to
58+
the TTL but since it's seems that no one uses it, neither am I... */
59+
if (ttlp)
60+
*ttlp = 0;
61+
62+
if (af == AF_UNSPEC)
63+
af = AF_INET;
64+
65+
(void)buf;
66+
(void)buflen;
67+
68+
char *stripped_name = strip_docker_local_domain(name);
69+
struct container *container = inspect_container(stripped_name, af);
70+
free(stripped_name);
71+
if (container == NULL)
72+
return NSS_STATUS_NOTFOUND;
73+
74+
char **r_aliases = xcalloc(2, sizeof(char *));
75+
r_aliases[0] = container->short_uuid;
76+
77+
result->h_name = container->name;
78+
result->h_aliases = r_aliases;
79+
result->h_addrtype = af;
80+
result->h_length = 4;
81+
result->h_addr_list = container->addr_list;
82+
83+
*errnop = 0;
84+
*h_errnop = 0;
85+
86+
/* I don't know for sure what canonp is, my guess is that it's a pointer to
87+
a list of "canonical names" */
88+
if (canonp)
89+
*canonp = result->h_name;
90+
91+
return NSS_STATUS_SUCCESS;
92+
}

Diff for: src/docker_nss.h

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
3+
#define PUBLIC __attribute__((visibility("default")))
4+
5+
#define MAX_NR_ALIASES 48
6+
#define MAX_NR_ADDRS 48
7+
8+
#include <nss.h>
9+
10+
enum nss_status
11+
PUBLIC
12+
_nss_docker_gethostbyname_r(const char *name,
13+
struct hostent *result,
14+
char *buf, size_t buflen,
15+
int *errnop, int *h_errnop);
16+
17+
enum nss_status
18+
PUBLIC
19+
_nss_docker_gethostbyname2_r(const char *name, int af,
20+
struct hostent *result,
21+
char *buf, size_t buflen,
22+
int *errnop, int *h_errnop);
23+
24+
enum nss_status
25+
PUBLIC
26+
_nss_docker_gethostbyname3_r(const char *name, int af,
27+
struct hostent *result,
28+
char *buf, size_t buflen,
29+
int *errnop, int *h_errnop,
30+
int32_t *ttlp, char **canonp);

0 commit comments

Comments
 (0)