aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/redirect-syslog.py
blob: e20fbd9e167c33a650d455ec6127e8cb8d7cdd1f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/env python3


import socket
import os
import struct
import time


sockr_path = '/etc/lxc/rebinds/devlog/log'


def get_namespace_from_proc(pid):
    '''Get lxc cgroup namespace name associated with pid, or None'''
    try:
        with open('/proc/%s/cgroup' % pid, 'rb') as fin:
            data = fin.read()
        data = data.splitlines()[0]
        truncation_index = data.index(b'/lxc/')
        return data[truncation_index+len(b'/lxc/'):]
    except (FileNotFoundError, ProcessLookupError, ValueError): pass


def get_namespace(pid, namespace_cache=dict(), timeout_time=30):
    '''
        Get cached namespace name (works only with /lxc/* cgroup names). If
        request is made on a name which is cached, the timeout of the name is
        reset. If the name is not found or the timeout is over, the name is
        got from proc and cached.

        There is a catch though: if there is pid overflow on the host system
        (pid restart from count 1) there might be a chance the pid is no
        longer on the same cached namespace name. But the chance are very
        unlikely and the consequences are ok for my usage. If this is
        important for you, you can set timeout_time to 0 (or rewrite this
                part). Thus, each time a line is received from /dev/log in
        a container, an open call is done to get the name which is bad for
        performances.
    '''
    tcur = time.time()
    name, timeout = namespace_cache.get(pid, (None, None))
    if name and timeout >= tcur:
        namespace_cache[pid][1] = tcur + timeout_time
        #print('debug: got cached name')
        return name
    else:
        name = get_namespace_from_proc(pid)
        timeout = tcur + timeout_time
        namespace_cache[pid] = [name, timeout]
        #print('debug: name cache miss')
    return name


def main():
    sockr = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    sockr.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
    os.unlink(sockr_path)
    sockr.bind(sockr_path)
    os.chmod(sockr_path, mode=0o666)
    socks = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    while True:
        data = sockr.recvmsg(8192, 256)

        pid = 0
        for cmsg in data[1]:
            if cmsg[0] == socket.SOL_SOCKET and cmsg[1] == socket.SCM_CREDENTIALS:
                pid = struct.unpack('i', cmsg[2][0:4])[0]

        data_mangled = data[0]
        name = pid and get_namespace(pid)
        if name:
            first_space_index = data[0].find(b' ')
            marker_end_index = data[0].find(b': ', first_space_index + 16)
            insert_index = data[0].rfind(b' ', 0, marker_end_index) + 1
            data_mangled = data[0][0:insert_index] + name + b'/' + data[0][insert_index:]

        #print('debug:', data_mangled)
        socks.sendto(data_mangled, '/dev/log')


if __name__ == '__main__':
    main()