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

RevLine 
[232]1/*
2 * ksyslog: In-kernel syslog receiver
3 * Copyright(C) 2013 Atzm WATANABE All rights reserved
4 * Distributed under the GPL
5 */
6
[246]7#include <linux/version.h>
[232]8#include <linux/module.h>
[243]9#include <linux/inet.h>
[232]10#include <linux/ip.h>
11#include <linux/udp.h>
12#include <linux/namei.h>
[263]13#include <linux/fsnotify.h>
[246]14#include <linux/proc_fs.h>
[250]15#include <linux/percpu.h>
[232]16#include <net/udp.h>
[246]17#include "compat.h"
[232]18#include "ksyslog.h"
19
[271]20static struct socket *ksyslog_rcv_sk;
21static struct workqueue_struct *ksyslog_wq;
22static struct ksyslog_queue __percpu *ksyslog_queue;
[232]23
24#ifdef CONFIG_PROC_FS
[271]25static struct proc_dir_entry *ksyslog_procdir;
26static struct proc_dir_entry *ksyslog_proc_size;
27static struct proc_dir_entry *ksyslog_proc_stats;
[232]28#endif
29
30static char *ksyslog_host = "0.0.0.0";
31static ushort ksyslog_port = 514;
32static char *ksyslog_path = "/var/log/ksyslog.log";
[250]33static ulong ksyslog_queue_size_max = 4096;
[232]34
35module_param(ksyslog_host, charp, 0444);
36module_param(ksyslog_port, ushort, 0444);
37module_param(ksyslog_path, charp, 0644);
[250]38module_param(ksyslog_queue_size_max, ulong, 0644);
[232]39
[250]40static int
[271]41ksyslog_queue_init(void (*handler)(struct work_struct *))
[241]42{
[271]43        int cpu;
44        struct ksyslog_queue *q;
45
46        ksyslog_queue = alloc_percpu(struct ksyslog_queue);
47        if (unlikely(!ksyslog_queue))
[250]48                return -ENOMEM;
[271]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
[250]62        return 0;
[241]63}
64
[250]65static void
[271]66ksyslog_queue_uninit(void)
[250]67{
[271]68        if (likely(ksyslog_queue))
69                free_percpu(ksyslog_queue);
70        ksyslog_queue = NULL;
[250]71}
72
[232]73static int
74ksyslog_close(struct file *file)
75{
[263]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;
[232]86}
87
88static struct file *
89ksyslog_open(const char *path)
90{
91        struct file *file;
[243]92        struct path ppath;
[232]93        mm_segment_t oldfs;
94
95        oldfs = get_fs();
96        set_fs(get_ds());
97
[243]98        if (unlikely(kern_path(path, LOOKUP_OPEN|LOOKUP_FOLLOW, &ppath)))
[232]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
[234]103        if (unlikely(IS_ERR(file)))
[232]104                goto out;
105
[264]106        compat_fsnotify_open(file);
[263]107
[234]108        if (unlikely(S_ISDIR(file->f_path.dentry->d_inode->i_mode))) {
[232]109                ksyslog_close(file);
110                file = ERR_PTR(-EISDIR);
111                goto out;
112        }
113
[234]114        if (unlikely(file->f_pos < 0)) {
[232]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
[234]141ksyslog_drop_warning(const struct ksyslog_entry *entry)
[232]142{
[268]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);
[232]150}
151
152static struct ksyslog_entry *
[234]153ksyslog_entry_create(const struct sk_buff *skb,
154                     const struct iphdr *iph, const struct udphdr *udph)
[232]155{
156        struct ksyslog_entry *entry;
[235]157        unsigned int priority, facility, severity, month, day, hour, minute, second;
158        unsigned char *start, month_s[4];
159        struct tm tm;
[232]160        int length, i;
161
[235]162        if (sscanf(skb->data, "<%3u>%3s %2u %2u:%2u:%2u ",
163                   &priority, month_s, &day, &hour, &minute, &second) != 6)
[232]164                return ERR_PTR(-EINVAL);
165
166        start = memchr(skb->data, '>', 5);
[271]167        if (!start)
[232]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
[235]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
[232]191        entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
[271]192        if (unlikely(!entry))
[232]193                return ERR_PTR(-ENOMEM);
194
195        length = skb->len - (start - skb->data);
196        entry->data = kzalloc(length, GFP_ATOMIC);
[271]197        if (unlikely(!entry->data)) {
[232]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
[235]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;
[234]211        entry->facility = facility;
212        entry->severity = severity;
213
[232]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++)
[234]224                if (unlikely(entry->data[i] == '\n'))
[232]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{
[250]241        if (unlikely(atomic64_read(&queue->size) >= ksyslog_queue_size_max))
[232]242                return -ENOBUFS;
243        list_add_tail_rcu(&entry->list, &queue->head);
[250]244        WARN_ON(atomic64_inc_return(&queue->size) > ksyslog_queue_size_max);
[232]245        return 0;
246}
247
248static void
249ksyslog_entry_del(struct ksyslog_queue *queue, struct ksyslog_entry *entry, bool free)
250{
[250]251        WARN_ON(atomic64_dec_return(&queue->size) < 0);
[232]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
[268]266static int
267ksyslog_entry_format(char **buf, const struct ksyslog_entry *entry)
[232]268{
[268]269        *buf = kzalloc(54 + entry->length + 2, GFP_ATOMIC);
[271]270        if (unlikely(!*buf))
[268]271                return -ENOMEM;
[232]272
[268]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;
[232]296        }
[268]297
298        kfree(buf);
299        return true;
[232]300}
301
302static void
303ksyslog_work_handler(struct work_struct *work)
304{
[271]305        struct file *file;
[268]306        struct ksyslog_entry *entry;
[271]307        struct ksyslog_queue *q;
[232]308
[271]309        q = container_of(work, struct ksyslog_queue, work);
310
[268]311        file = ksyslog_open(ksyslog_path);
312        if (unlikely(IS_ERR(file)))
[271]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);
[250]319                goto out;
[271]320        }
321        ksyslog_entry_del(q, entry, false);
322        spin_unlock_bh(&q->lock);
[232]323
[271]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);
[232]329        }
[234]330
[271]331        call_rcu(&entry->rcu, ksyslog_entry_free);
[232]332
333out:
[271]334        ksyslog_close(file);
335
336        if (atomic64_read(&q->size) > 0)
337                queue_work(ksyslog_wq, work);
[232]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;
[271]347        struct ksyslog_queue *q;
[232]348
[271]349        q = per_cpu_ptr(ksyslog_queue, smp_processor_id());
350
[250]351        if (unlikely(skb_linearize(skb))) {
[271]352                ksyslog_stats_add(&q->drop_stats, skb->len);
[250]353                goto out;
354        }
[232]355
356        iph = ip_hdr(skb);
357        udph = udp_hdr(skb);
358
[250]359        if (unlikely(!skb_pull(skb, sizeof(*udph)))) {
[271]360                ksyslog_stats_add(&q->drop_stats, skb->len);
[250]361                goto out;
362        }
[232]363
364        entry = ksyslog_entry_create(skb, iph, udph);
[250]365        if (unlikely(IS_ERR(entry))) {
366                if (PTR_ERR(entry) == -EINVAL) {
[271]367                        ksyslog_stats_add(&q->discard_stats, skb->len);
[250]368                        goto out;
369                }
[232]370
[271]371                ksyslog_stats_add(&q->drop_stats, skb->len);
[250]372                goto out;
373        }
374
[271]375        spin_lock_bh(&q->lock);
376        err = ksyslog_entry_add(q, entry);
377        spin_unlock_bh(&q->lock);
[232]378
[241]379        if (unlikely(err)) {
[271]380                ksyslog_stats_add(&q->drop_stats, entry->length);
[241]381                ksyslog_drop_warning(entry);
[232]382                ksyslog_entry_free(&entry->rcu);
[250]383                goto out;
[241]384        }
[232]385
[271]386        queue_work(ksyslog_wq, &q->work);
387
[232]388out:
389        consume_skb(skb);
390        return 0;
391}
392
393#ifdef CONFIG_PROC_FS
394static int
[271]395ksyslog_size_seq_show(struct seq_file *seq, void *v)
[232]396{
[271]397        int cpu;
398        struct ksyslog_queue *q;
[232]399
[271]400        seq_puts(seq, "{\n");
[232]401
[271]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        }
[232]406
[271]407        seq_puts(seq, "}\n");
[240]408        return 0;
409}
410
411static int
[250]412ksyslog_size_seq_open(struct inode *inode, struct file *file)
[240]413{
[250]414        return single_open(file, ksyslog_size_seq_show, PDE_DATA(inode));
[240]415}
416
[241]417static int
[250]418ksyslog_stats_seq_show(struct seq_file *seq, void *v)
[241]419{
[271]420        int cpu;
421        struct ksyslog_queue *q;
[241]422
[271]423        seq_puts(seq, "{\n");
[241]424
[271]425        for_each_possible_cpu(cpu) {
426                q = per_cpu_ptr(ksyslog_queue, cpu);
[250]427
[271]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");
[250]442        }
443
[271]444        seq_puts(seq, "}\n");
[241]445        return 0;
446}
447
448static int
[250]449ksyslog_stats_seq_open(struct inode *inode, struct file *file)
[241]450{
[250]451        return single_open(file, ksyslog_stats_seq_show, PDE_DATA(inode));
[241]452}
453
[250]454static struct file_operations ksyslog_size_fops = {
[240]455        .owner   = THIS_MODULE,
[250]456        .open    = ksyslog_size_seq_open,
[240]457        .read    = seq_read,
458        .llseek  = seq_lseek,
459        .release = single_release,
460};
461
[250]462static struct file_operations ksyslog_stats_fops = {
[241]463        .owner   = THIS_MODULE,
[250]464        .open    = ksyslog_stats_seq_open,
[241]465        .read    = seq_read,
466        .llseek  = seq_lseek,
467        .release = single_release,
468};
469
470static void
471ksyslog_proc_destroy(void)
472{
[250]473        if (ksyslog_proc_size)
474                remove_proc_entry("size", ksyslog_procdir);
475        ksyslog_proc_size = NULL;
[241]476
[250]477        if (ksyslog_proc_stats)
478                remove_proc_entry("stats", ksyslog_procdir);
479        ksyslog_proc_stats = NULL;
[241]480
481        if (ksyslog_procdir)
[246]482                remove_proc_entry("ksyslog", NULL);
[241]483        ksyslog_procdir = NULL;
484}
485
[240]486static int
[232]487ksyslog_proc_init(void)
488{
489        ksyslog_procdir = proc_mkdir("ksyslog", NULL);
[271]490        if (!ksyslog_procdir) {
[250]491                pr_err("ksyslog: proc_mkdir failed\n");
[241]492                goto err;
[232]493        }
494
[250]495        ksyslog_proc_size = proc_create("size", S_IRUGO, ksyslog_procdir,
496                                        &ksyslog_size_fops);
[271]497        if (!ksyslog_proc_size) {
[250]498                pr_err("ksyslog: proc_create(size) failed\n");
[241]499                goto err;
[240]500        }
501
[250]502        ksyslog_proc_stats = proc_create("stats", S_IRUGO, ksyslog_procdir,
503                                         &ksyslog_stats_fops);
[271]504        if (!ksyslog_proc_stats) {
[250]505                pr_err("ksyslog: proc_create(stats) failed\n");
[241]506                goto err;
507        }
[232]508
[241]509        return 0;
[232]510
[241]511err:
512        ksyslog_proc_destroy();
513        return -ENOMEM;
[232]514}
515#endif
516
517static void
518ksyslog_finish(void)
519{
[271]520        int cpu;
521
[232]522        if (ksyslog_rcv_sk)
523                sock_release(ksyslog_rcv_sk);
524        ksyslog_rcv_sk = NULL;
525
[271]526        if (ksyslog_wq)
[232]527                destroy_workqueue(ksyslog_wq);
528        ksyslog_wq = NULL;
529
530#ifdef CONFIG_PROC_FS
531        ksyslog_proc_destroy();
532#endif
533
[271]534        for_each_possible_cpu(cpu)
535                ksyslog_entry_destroy(per_cpu_ptr(ksyslog_queue, cpu));
[237]536        rcu_barrier();
[250]537
[271]538        ksyslog_queue_uninit();
[232]539}
540
541static int __init
542ksyslog_init(void)
543{
[270]544        int err;
[232]545        struct sockaddr_in sin;
546
[271]547        err = ksyslog_queue_init(ksyslog_work_handler);
[250]548        if (err)
549                goto err;
[232]550
551#ifdef CONFIG_PROC_FS
552        err = ksyslog_proc_init();
553        if (err)
554                goto err;
555#endif
556
[268]557        ksyslog_wq = create_workqueue("ksyslog");
[271]558        if (!ksyslog_wq) {
[250]559                pr_err("ksyslog: create_workqueue failed\n");
[232]560                err = -ENOMEM;
561                goto err;
562        }
563
564        err = sock_create(AF_INET, SOCK_DGRAM, 0, &ksyslog_rcv_sk);
565        if (err) {
[250]566                pr_err("ksyslog: sock_create failed\n");
[232]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) {
[250]577                pr_err("ksyslog: kernel_bind failed\n");
[232]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;
[247]583        udp_encap_enable();
[232]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.