source: ksyslog/trunk/ksyslog.c @ 271

Revision 271, 13.1 KB checked in by atzm, 10 years ago (diff)

improve performance on multi-queue environment

Line 
1/*
2 * ksyslog: In-kernel syslog receiver
3 * Copyright(C) 2013 Atzm WATANABE All rights reserved
4 * Distributed under the GPL
5 */
6
7#include <linux/version.h>
8#include <linux/module.h>
9#include <linux/inet.h>
10#include <linux/ip.h>
11#include <linux/udp.h>
12#include <linux/namei.h>
13#include <linux/fsnotify.h>
14#include <linux/proc_fs.h>
15#include <linux/percpu.h>
16#include <net/udp.h>
17#include "compat.h"
18#include "ksyslog.h"
19
20static struct socket *ksyslog_rcv_sk;
21static struct workqueue_struct *ksyslog_wq;
22static struct ksyslog_queue __percpu *ksyslog_queue;
23
24#ifdef CONFIG_PROC_FS
25static struct proc_dir_entry *ksyslog_procdir;
26static struct proc_dir_entry *ksyslog_proc_size;
27static struct proc_dir_entry *ksyslog_proc_stats;
28#endif
29
30static char *ksyslog_host = "0.0.0.0";
31static ushort ksyslog_port = 514;
32static char *ksyslog_path = "/var/log/ksyslog.log";
33static ulong ksyslog_queue_size_max = 4096;
34
35module_param(ksyslog_host, charp, 0444);
36module_param(ksyslog_port, ushort, 0444);
37module_param(ksyslog_path, charp, 0644);
38module_param(ksyslog_queue_size_max, ulong, 0644);
39
40static int
41ksyslog_queue_init(void (*handler)(struct work_struct *))
42{
43        int cpu;
44        struct ksyslog_queue *q;
45
46        ksyslog_queue = alloc_percpu(struct ksyslog_queue);
47        if (unlikely(!ksyslog_queue))
48                return -ENOMEM;
49
50        for_each_possible_cpu(cpu) {
51                q = per_cpu_ptr(ksyslog_queue, cpu);
52
53                INIT_LIST_HEAD(&q->head);
54                INIT_WORK(&q->work, handler);
55                spin_lock_init(&q->lock);
56                atomic64_set(&q->size, 0);
57                ksyslog_stats_zero(&q->write_stats);
58                ksyslog_stats_zero(&q->drop_stats);
59                ksyslog_stats_zero(&q->discard_stats);
60        }
61
62        return 0;
63}
64
65static void
66ksyslog_queue_uninit(void)
67{
68        if (likely(ksyslog_queue))
69                free_percpu(ksyslog_queue);
70        ksyslog_queue = NULL;
71}
72
73static int
74ksyslog_close(struct file *file)
75{
76        int err;
77        mm_segment_t oldfs;
78
79        oldfs = get_fs();
80        set_fs(get_ds());
81
82        err = filp_close(file, NULL);
83
84        set_fs(oldfs);
85        return err;
86}
87
88static struct file *
89ksyslog_open(const char *path)
90{
91        struct file *file;
92        struct path ppath;
93        mm_segment_t oldfs;
94
95        oldfs = get_fs();
96        set_fs(get_ds());
97
98        if (unlikely(kern_path(path, LOOKUP_OPEN|LOOKUP_FOLLOW, &ppath)))
99                file = filp_open(path, O_CREAT|O_WRONLY|O_APPEND|O_LARGEFILE, 0600);
100        else
101                file = filp_open(path, O_WRONLY|O_APPEND|O_LARGEFILE, 0);
102
103        if (unlikely(IS_ERR(file)))
104                goto out;
105
106        compat_fsnotify_open(file);
107
108        if (unlikely(S_ISDIR(file->f_path.dentry->d_inode->i_mode))) {
109                ksyslog_close(file);
110                file = ERR_PTR(-EISDIR);
111                goto out;
112        }
113
114        if (unlikely(file->f_pos < 0)) {
115                ksyslog_close(file);
116                file = ERR_PTR(-EIO);
117                goto out;
118        }
119
120out:
121        set_fs(oldfs);
122        return file;
123}
124
125static int
126ksyslog_write(struct file *file, const char *buf, const size_t length)
127{
128        int err;
129        mm_segment_t oldfs;
130
131        oldfs = get_fs();
132        set_fs(get_ds());
133
134        err = vfs_write(file, (__force void __user *)buf, length, &file->f_pos);
135
136        set_fs(oldfs);
137        return err;
138}
139
140static void
141ksyslog_drop_warning(const struct ksyslog_entry *entry)
142{
143        net_warn_ratelimited("ksyslog: dropped: %llu %s.%s %u.%u.%u.%u %.*s\n",
144                             timeval_to_ns(&entry->tv) / 1000 / 1000 / 1000,
145                             ksyslog_facility_str(entry->facility),
146                             ksyslog_severity_str(entry->severity),
147                             entry->saddr.addr8[0], entry->saddr.addr8[1],
148                             entry->saddr.addr8[2], entry->saddr.addr8[3],
149                             (int)entry->length, entry->data);
150}
151
152static struct ksyslog_entry *
153ksyslog_entry_create(const struct sk_buff *skb,
154                     const struct iphdr *iph, const struct udphdr *udph)
155{
156        struct ksyslog_entry *entry;
157        unsigned int priority, facility, severity, month, day, hour, minute, second;
158        unsigned char *start, month_s[4];
159        struct tm tm;
160        int length, i;
161
162        if (sscanf(skb->data, "<%3u>%3s %2u %2u:%2u:%2u ",
163                   &priority, month_s, &day, &hour, &minute, &second) != 6)
164                return ERR_PTR(-EINVAL);
165
166        start = memchr(skb->data, '>', 5);
167        if (!start)
168                return ERR_PTR(-EINVAL);
169        start++;
170
171        facility = priority >> 3;
172        severity = priority & 7;
173
174        if (facility >= __KSYSLOG_F_MAX)
175                return ERR_PTR(-EINVAL);
176        if (severity >= __KSYSLOG_S_MAX)
177                return ERR_PTR(-EINVAL);
178
179        month = ksyslog_month_num(month_s);
180        if (!month)
181                return ERR_PTR(-EINVAL);
182        if (day > 31)
183                return ERR_PTR(-EINVAL);
184        if (hour > 23)
185                return ERR_PTR(-EINVAL);
186        if (minute > 59)
187                return ERR_PTR(-EINVAL);
188        if (second > 59)
189                return ERR_PTR(-EINVAL);
190
191        entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
192        if (unlikely(!entry))
193                return ERR_PTR(-ENOMEM);
194
195        length = skb->len - (start - skb->data);
196        entry->data = kzalloc(length, GFP_ATOMIC);
197        if (unlikely(!entry->data)) {
198                kfree(entry);
199                return ERR_PTR(-ENOMEM);
200        }
201
202        if (skb->tstamp.tv64)
203                entry->tv = ktime_to_timeval(skb->tstamp);
204        else
205                do_gettimeofday(&entry->tv);
206
207        time_to_tm(entry->tv.tv_sec, 0, &tm);
208        entry->time = mktime(tm.tm_year + 1900, month, day, hour, minute, second);
209
210        entry->priority = priority;
211        entry->facility = facility;
212        entry->severity = severity;
213
214        entry->daddr.addr32 = iph->daddr;
215        entry->saddr.addr32 = iph->saddr;
216
217        entry->dport = udph->dest;
218        entry->sport = udph->source;
219
220        entry->length = length;
221        memcpy(entry->data, start, length);
222
223        for (i = 0; i < length; i++)
224                if (unlikely(entry->data[i] == '\n'))
225                        entry->data[i] = ' ';
226
227        return entry;
228}
229
230static void
231ksyslog_entry_free(struct rcu_head *head)
232{
233        struct ksyslog_entry *entry = container_of(head, struct ksyslog_entry, rcu);
234        kfree(entry->data);
235        kfree(entry);
236}
237
238static int
239ksyslog_entry_add(struct ksyslog_queue *queue, struct ksyslog_entry *entry)
240{
241        if (unlikely(atomic64_read(&queue->size) >= ksyslog_queue_size_max))
242                return -ENOBUFS;
243        list_add_tail_rcu(&entry->list, &queue->head);
244        WARN_ON(atomic64_inc_return(&queue->size) > ksyslog_queue_size_max);
245        return 0;
246}
247
248static void
249ksyslog_entry_del(struct ksyslog_queue *queue, struct ksyslog_entry *entry, bool free)
250{
251        WARN_ON(atomic64_dec_return(&queue->size) < 0);
252        list_del_rcu(&entry->list);
253        if (free)
254                call_rcu(&entry->rcu, ksyslog_entry_free);
255}
256
257static void
258ksyslog_entry_destroy(struct ksyslog_queue *queue)
259{
260        struct ksyslog_entry *entry, *next;
261
262        list_for_each_entry_safe(entry, next, &queue->head, list)
263                ksyslog_entry_del(queue, entry, true);
264}
265
266static int
267ksyslog_entry_format(char **buf, const struct ksyslog_entry *entry)
268{
269        *buf = kzalloc(54 + entry->length + 2, GFP_ATOMIC);
270        if (unlikely(!*buf))
271                return -ENOMEM;
272
273        return sprintf(*buf, "%llu %s.%s %u.%u.%u.%u %.*s\n",
274                       timeval_to_ns(&entry->tv) / 1000 / 1000 / 1000,
275                       ksyslog_facility_str(entry->facility),
276                       ksyslog_severity_str(entry->severity),
277                       entry->saddr.addr8[0], entry->saddr.addr8[1],
278                       entry->saddr.addr8[2], entry->saddr.addr8[3],
279                       (int)entry->length, entry->data);
280}
281
282static bool
283ksyslog_entry_write(struct file *file, struct ksyslog_entry *entry)
284{
285        int length;
286        char *buf;
287
288        length = ksyslog_entry_format(&buf, entry);
289
290        if (unlikely(length < 0))
291                return false;
292
293        if (unlikely(ksyslog_write(file, buf, length) != length)) {
294                kfree(buf);
295                return false;
296        }
297
298        kfree(buf);
299        return true;
300}
301
302static void
303ksyslog_work_handler(struct work_struct *work)
304{
305        struct file *file;
306        struct ksyslog_entry *entry;
307        struct ksyslog_queue *q;
308
309        q = container_of(work, struct ksyslog_queue, work);
310
311        file = ksyslog_open(ksyslog_path);
312        if (unlikely(IS_ERR(file)))
313                return;
314
315        spin_lock_bh(&q->lock);
316        entry = list_first_or_null_rcu(&q->head, struct ksyslog_entry, list);
317        if (unlikely(!entry)) {
318                spin_unlock_bh(&q->lock);
319                goto out;
320        }
321        ksyslog_entry_del(q, entry, false);
322        spin_unlock_bh(&q->lock);
323
324        if (likely(ksyslog_entry_write(file, entry))) {
325                ksyslog_stats_add(&q->write_stats, entry->length);
326        } else {
327                ksyslog_stats_add(&q->drop_stats, entry->length);
328                ksyslog_drop_warning(entry);
329        }
330
331        call_rcu(&entry->rcu, ksyslog_entry_free);
332
333out:
334        ksyslog_close(file);
335
336        if (atomic64_read(&q->size) > 0)
337                queue_work(ksyslog_wq, work);
338}
339
340static int
341ksyslog_rcv(struct sock *sk, struct sk_buff *skb)
342{
343        int err;
344        struct iphdr *iph;
345        struct udphdr *udph;
346        struct ksyslog_entry *entry;
347        struct ksyslog_queue *q;
348
349        q = per_cpu_ptr(ksyslog_queue, smp_processor_id());
350
351        if (unlikely(skb_linearize(skb))) {
352                ksyslog_stats_add(&q->drop_stats, skb->len);
353                goto out;
354        }
355
356        iph = ip_hdr(skb);
357        udph = udp_hdr(skb);
358
359        if (unlikely(!skb_pull(skb, sizeof(*udph)))) {
360                ksyslog_stats_add(&q->drop_stats, skb->len);
361                goto out;
362        }
363
364        entry = ksyslog_entry_create(skb, iph, udph);
365        if (unlikely(IS_ERR(entry))) {
366                if (PTR_ERR(entry) == -EINVAL) {
367                        ksyslog_stats_add(&q->discard_stats, skb->len);
368                        goto out;
369                }
370
371                ksyslog_stats_add(&q->drop_stats, skb->len);
372                goto out;
373        }
374
375        spin_lock_bh(&q->lock);
376        err = ksyslog_entry_add(q, entry);
377        spin_unlock_bh(&q->lock);
378
379        if (unlikely(err)) {
380                ksyslog_stats_add(&q->drop_stats, entry->length);
381                ksyslog_drop_warning(entry);
382                ksyslog_entry_free(&entry->rcu);
383                goto out;
384        }
385
386        queue_work(ksyslog_wq, &q->work);
387
388out:
389        consume_skb(skb);
390        return 0;
391}
392
393#ifdef CONFIG_PROC_FS
394static int
395ksyslog_size_seq_show(struct seq_file *seq, void *v)
396{
397        int cpu;
398        struct ksyslog_queue *q;
399
400        seq_puts(seq, "{\n");
401
402        for_each_possible_cpu(cpu) {
403                q = per_cpu_ptr(ksyslog_queue, cpu);
404                seq_printf(seq, \"%u\": \"%lu\",\n", cpu, atomic64_read(&q->size));
405        }
406
407        seq_puts(seq, "}\n");
408        return 0;
409}
410
411static int
412ksyslog_size_seq_open(struct inode *inode, struct file *file)
413{
414        return single_open(file, ksyslog_size_seq_show, PDE_DATA(inode));
415}
416
417static int
418ksyslog_stats_seq_show(struct seq_file *seq, void *v)
419{
420        int cpu;
421        struct ksyslog_queue *q;
422
423        seq_puts(seq, "{\n");
424
425        for_each_possible_cpu(cpu) {
426                q = per_cpu_ptr(ksyslog_queue, cpu);
427
428                seq_printf(seq, \"%u\": {\n", cpu);
429                seq_puts(seq,   "    \"write\": {\n");
430                seq_printf(seq, "      \"bytes\":   \"%lu\",\n", atomic64_read(&q->write_stats.bytes));
431                seq_printf(seq, "      \"packets\": \"%lu\",\n", atomic64_read(&q->write_stats.packets));
432                seq_puts(seq,   "    },\n");
433                seq_puts(seq,   "    \"drop\": {\n");
434                seq_printf(seq, "      \"bytes\":   \"%lu\",\n", atomic64_read(&q->drop_stats.bytes));
435                seq_printf(seq, "      \"packets\": \"%lu\",\n", atomic64_read(&q->drop_stats.packets));
436                seq_puts(seq,   "    },\n");
437                seq_puts(seq,   "    \"discard\": {\n");
438                seq_printf(seq, "      \"bytes\":   \"%lu\",\n", atomic64_read(&q->discard_stats.bytes));
439                seq_printf(seq, "      \"packets\": \"%lu\",\n", atomic64_read(&q->discard_stats.packets));
440                seq_puts(seq,   "    },\n");
441                seq_puts(seq,   "  },\n");
442        }
443
444        seq_puts(seq, "}\n");
445        return 0;
446}
447
448static int
449ksyslog_stats_seq_open(struct inode *inode, struct file *file)
450{
451        return single_open(file, ksyslog_stats_seq_show, PDE_DATA(inode));
452}
453
454static struct file_operations ksyslog_size_fops = {
455        .owner   = THIS_MODULE,
456        .open    = ksyslog_size_seq_open,
457        .read    = seq_read,
458        .llseek  = seq_lseek,
459        .release = single_release,
460};
461
462static struct file_operations ksyslog_stats_fops = {
463        .owner   = THIS_MODULE,
464        .open    = ksyslog_stats_seq_open,
465        .read    = seq_read,
466        .llseek  = seq_lseek,
467        .release = single_release,
468};
469
470static void
471ksyslog_proc_destroy(void)
472{
473        if (ksyslog_proc_size)
474                remove_proc_entry("size", ksyslog_procdir);
475        ksyslog_proc_size = NULL;
476
477        if (ksyslog_proc_stats)
478                remove_proc_entry("stats", ksyslog_procdir);
479        ksyslog_proc_stats = NULL;
480
481        if (ksyslog_procdir)
482                remove_proc_entry("ksyslog", NULL);
483        ksyslog_procdir = NULL;
484}
485
486static int
487ksyslog_proc_init(void)
488{
489        ksyslog_procdir = proc_mkdir("ksyslog", NULL);
490        if (!ksyslog_procdir) {
491                pr_err("ksyslog: proc_mkdir failed\n");
492                goto err;
493        }
494
495        ksyslog_proc_size = proc_create("size", S_IRUGO, ksyslog_procdir,
496                                        &ksyslog_size_fops);
497        if (!ksyslog_proc_size) {
498                pr_err("ksyslog: proc_create(size) failed\n");
499                goto err;
500        }
501
502        ksyslog_proc_stats = proc_create("stats", S_IRUGO, ksyslog_procdir,
503                                         &ksyslog_stats_fops);
504        if (!ksyslog_proc_stats) {
505                pr_err("ksyslog: proc_create(stats) failed\n");
506                goto err;
507        }
508
509        return 0;
510
511err:
512        ksyslog_proc_destroy();
513        return -ENOMEM;
514}
515#endif
516
517static void
518ksyslog_finish(void)
519{
520        int cpu;
521
522        if (ksyslog_rcv_sk)
523                sock_release(ksyslog_rcv_sk);
524        ksyslog_rcv_sk = NULL;
525
526        if (ksyslog_wq)
527                destroy_workqueue(ksyslog_wq);
528        ksyslog_wq = NULL;
529
530#ifdef CONFIG_PROC_FS
531        ksyslog_proc_destroy();
532#endif
533
534        for_each_possible_cpu(cpu)
535                ksyslog_entry_destroy(per_cpu_ptr(ksyslog_queue, cpu));
536        rcu_barrier();
537
538        ksyslog_queue_uninit();
539}
540
541static int __init
542ksyslog_init(void)
543{
544        int err;
545        struct sockaddr_in sin;
546
547        err = ksyslog_queue_init(ksyslog_work_handler);
548        if (err)
549                goto err;
550
551#ifdef CONFIG_PROC_FS
552        err = ksyslog_proc_init();
553        if (err)
554                goto err;
555#endif
556
557        ksyslog_wq = create_workqueue("ksyslog");
558        if (!ksyslog_wq) {
559                pr_err("ksyslog: create_workqueue failed\n");
560                err = -ENOMEM;
561                goto err;
562        }
563
564        err = sock_create(AF_INET, SOCK_DGRAM, 0, &ksyslog_rcv_sk);
565        if (err) {
566                pr_err("ksyslog: sock_create failed\n");
567                goto err;
568        }
569
570        sin.sin_family = AF_INET;
571        sin.sin_addr.s_addr = in_aton(ksyslog_host);
572        sin.sin_port = htons(ksyslog_port);
573
574        err = kernel_bind(ksyslog_rcv_sk, (struct sockaddr *)&sin,
575                          sizeof(struct sockaddr_in));
576        if (err) {
577                pr_err("ksyslog: kernel_bind failed\n");
578                goto err;
579        }
580
581        udp_sk(ksyslog_rcv_sk->sk)->encap_type = UDP_ENCAP_KSYSLOG;
582        udp_sk(ksyslog_rcv_sk->sk)->encap_rcv = ksyslog_rcv;
583        udp_encap_enable();
584
585        return 0;
586
587err:
588        ksyslog_finish();
589        return err;
590}
591
592static void __exit
593ksyslog_exit(void)
594{
595        ksyslog_finish();
596}
597
598module_init(ksyslog_init);
599module_exit(ksyslog_exit);
600
601MODULE_AUTHOR("Atzm WATANABE");
602MODULE_DESCRIPTION("In-kernel syslog receiver");
603MODULE_LICENSE("GPL");
Note: See TracBrowser for help on using the repository browser.