From 2a7b582eb17381cd0142be5b588e36c78ec792df Mon Sep 17 00:00:00 2001 From: hartkopp Date: Thu, 18 Feb 2010 18:37:20 +0000 Subject: [PATCH] Added netlink powered CAN gateway functionality with CAN frame modification features. TODO: Implement removal of single routing entries / Dump complete entries with packet counters. git-svn-id: svn://svn.berlios.de//socketcan/trunk@1127 030b6a49-0b11-0410-94ab-b0dab22257f2 --- kernel/2.6/include/socketcan/can/gw.h | 74 ++++ kernel/2.6/net/can/Makefile | 4 + kernel/2.6/net/can/gw.c | 483 ++++++++++++++++++++++++++ test/Makefile | 1 + test/gwtest.c | 117 +++++++ 5 files changed, 679 insertions(+) create mode 100644 kernel/2.6/include/socketcan/can/gw.h create mode 100644 kernel/2.6/net/can/gw.c create mode 100644 test/gwtest.c diff --git a/kernel/2.6/include/socketcan/can/gw.h b/kernel/2.6/include/socketcan/can/gw.h new file mode 100644 index 0000000..20feea3 --- /dev/null +++ b/kernel/2.6/include/socketcan/can/gw.h @@ -0,0 +1,74 @@ +/* + * socketcan/can/gw.h + * + * Definitions for CAN frame Gateway/Router/Bridge + * + * $Id$ + * + * Author: Oliver Hartkopp + * Copyright (c) 2002-2010 Volkswagen Group Electronic Research + * All rights reserved. + * + * Send feedback to + * + */ + +#ifndef CAN_GW_H +#define CAN_GW_H + +#include + +struct rtcanmsg { + __u8 can_family; + __u8 can_txflags; + __u16 pad; + __u32 src_ifindex; + __u32 dst_ifindex; +}; + +#define CAN_GW_TXFLAGS_LOOPBACK 0x01 + +/* CAN rtnetlink attribute definitions */ +enum { + CGW_UNSPEC, + CGW_FILTER, /* specify struct can_filter on source CAN device */ + CGW_MOD_AND, /* CAN frame modification binary AND */ + CGW_MOD_OR, /* CAN frame modification binary OR */ + CGW_MOD_XOR, /* CAN frame modification binary XOR */ + CGW_MOD_SET, /* CAN frame modification set alternate values */ + __CGW_MAX +}; + +#define CGW_MAX (__CGW_MAX - 1) + +#define CGW_MOD_FUNCS 4 /* AND OR XOR SET */ + +/* CAN frame elements that are affected by curr. 3 CAN frame modifications */ +#define CGW_MOD_ID 0x01 +#define CGW_MOD_DLC 0x02 +#define CGW_MOD_DATA 0x04 + +#define CGW_FRAME_MODS 3 /* ID DLC DATA */ + +#define MAX_MODFUNCTIONS (CGW_MOD_FUNCS * CGW_FRAME_MODS) + +#define CGW_MODATTR_LEN (sizeof(struct can_frame) + 1) + +/* + * CAN rtnetlink attribute contents in detail + * + * CGW_FILTER (length 32 bytes): + * Sets a CAN receive filter for the gateway job specified by the + * struct can_filter described in include/linux/can.h + * + * CGW_MOD_XXX (length 17 bytes): + * Specifies a modification that's done to a received CAN frame before it is + * send out to the destination interface. + * + * affected CAN frame elements + * data used as operator + * + * Remark: The attribute data is a linear buffer. Beware of sending structs! + */ + +#endif diff --git a/kernel/2.6/net/can/Makefile b/kernel/2.6/net/can/Makefile index 38d09ff..065fdbc 100644 --- a/kernel/2.6/net/can/Makefile +++ b/kernel/2.6/net/can/Makefile @@ -15,6 +15,7 @@ export CONFIG_CAN=m export CONFIG_CAN_RAW=m export CONFIG_CAN_BCM=m export CONFIG_CAN_ISOTP=m +export CONFIG_CAN_GW=m else @@ -32,4 +33,7 @@ can-bcm-objs := bcm.o obj-$(CONFIG_CAN_ISOTP) += can-isotp.o can-isotp-objs := isotp.o +obj-$(CONFIG_CAN_GW) += can-gw.o +can-gw-objs := gw.o + endif diff --git a/kernel/2.6/net/can/gw.c b/kernel/2.6/net/can/gw.c new file mode 100644 index 0000000..5ccf0c2 --- /dev/null +++ b/kernel/2.6/net/can/gw.c @@ -0,0 +1,483 @@ +/* + * gw.c - CAN frame Gateway/Router/Bridge with netlink interface + * + * Copyright (c) 2002-2010 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* for RCSID. Removed by mkpatch script */ +RCSID("$Id$"); + +#define CAN_GW_VERSION "20100218" +static __initdata const char banner[] = + KERN_INFO "can: netlink gateway (rev " CAN_GW_VERSION ")\n"; + +MODULE_DESCRIPTION("PF_CAN netlink gateway"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Oliver Hartkopp "); + +HLIST_HEAD(can_gw_list); +static DEFINE_SPINLOCK(can_gw_list_lock); +static struct notifier_block notifier; + +static struct kmem_cache *gw_cache __read_mostly; + +#define GW_SK_MAGIC ((void *)(¬ifier)) + +/* + * So far we just support CAN -> CAN routing and frame modifications. + * + * The internal can_can_gw structure contains optional attributes for + * a CAN -> CAN gateway job. + */ +struct can_can_gw { + struct can_filter filter; + struct { + struct can_frame and; + struct can_frame or; + struct can_frame xor; + struct can_frame set; + } modframe; + void (*modfunc[MAX_MODFUNCTIONS])(struct can_frame *cf, + struct can_can_gw *mod); +}; + +/* list entry for CAN gateways jobs */ +struct gw_job { + struct hlist_node list; + struct rcu_head rcu; + struct net_device *src_dev; + struct net_device *dst_dev; + u32 flags; + u32 handled_frames; + u32 dropped_frames; + union { + struct can_can_gw ccgw; + /* tbc */ + }; +}; + +/* content of u32 gwjob.flags */ +#define CAN_TX_LOOPBACK 0x00000001 + +/* modification functions that are invoked in the hot path in gw_rcv */ +void mod_and_id (struct can_frame *cf, struct can_can_gw *mod) { + cf->can_id &= mod->modframe.and.can_id; +} +void mod_and_dlc (struct can_frame *cf, struct can_can_gw *mod) { + cf->can_dlc &= mod->modframe.and.can_dlc; +} +void mod_and_data (struct can_frame *cf, struct can_can_gw *mod) { + *(u64 *)cf->data &= *(u64 *)mod->modframe.and.data; +} +void mod_or_id (struct can_frame *cf, struct can_can_gw *mod) { + cf->can_id |= mod->modframe.or.can_id; +} +void mod_or_dlc (struct can_frame *cf, struct can_can_gw *mod) { + cf->can_dlc |= mod->modframe.or.can_dlc; +} +void mod_or_data (struct can_frame *cf, struct can_can_gw *mod) { + *(u64 *)cf->data |= *(u64 *)mod->modframe.or.data; +} +void mod_xor_id (struct can_frame *cf, struct can_can_gw *mod) { + cf->can_id ^= mod->modframe.xor.can_id; +} +void mod_xor_dlc (struct can_frame *cf, struct can_can_gw *mod) { + cf->can_dlc ^= mod->modframe.xor.can_dlc; +} +void mod_xor_data (struct can_frame *cf, struct can_can_gw *mod) { + *(u64 *)cf->data ^= *(u64 *)mod->modframe.xor.data; +} +void mod_set_id (struct can_frame *cf, struct can_can_gw *mod) { + cf->can_id = mod->modframe.set.can_id; +} +void mod_set_dlc (struct can_frame *cf, struct can_can_gw *mod) { + cf->can_dlc = mod->modframe.set.can_dlc; +} +void mod_set_data (struct can_frame *cf, struct can_can_gw *mod) { + *(u64 *)cf->data = *(u64 *)mod->modframe.set.data; +} + + +/* the receive & process & send function */ +static void gw_rcv(struct sk_buff *skb, void *data) +{ + struct gw_job *gwj = (struct gw_job *)data; + struct can_frame *cf; + struct sk_buff *nskb; + int modidx = 0; + + /* do not handle already routed frames */ + if (skb->sk == GW_SK_MAGIC) + return; + + if (!netif_running(gwj->dst_dev)) { + gwj->dropped_frames++; + return; + } + + /* + * clone the given skb, which has not been done in can_rv() + * + * When there is at least one modification function activated, + * we need to copy the skb as we want to modify skb->data. + */ + if (gwj->ccgw.modfunc[0]) + nskb = skb_copy(skb, GFP_ATOMIC); + else + nskb = skb_clone(skb, GFP_ATOMIC); + + if (!nskb) { + gwj->dropped_frames++; + return; + } + + gwj->handled_frames++; + + /* mark routed frames with a 'special' sk value */ + nskb->sk = GW_SK_MAGIC; + nskb->dev = gwj->dst_dev; + + /* pointer to modifiable CAN frame */ + cf = (struct can_frame *)nskb->data; + + /* perform preprocessed modification functions if there are any */ + while (modidx < MAX_MODFUNCTIONS && gwj->ccgw.modfunc[modidx]) + (*gwj->ccgw.modfunc[modidx++])(cf, &gwj->ccgw); + + /* send to netdevice */ + if (can_send(nskb, gwj->flags & CAN_TX_LOOPBACK)) + gwj->dropped_frames++; +} + +static inline int can_gw_register_filter(struct gw_job *gwj) +{ + return can_rx_register(gwj->src_dev, gwj->ccgw.filter.can_id, + gwj->ccgw.filter.can_mask, gw_rcv, gwj, "gw"); +} + +static inline void can_gw_unregister_filter(struct gw_job *gwj) +{ + can_rx_unregister(gwj->src_dev, gwj->ccgw.filter.can_id, + gwj->ccgw.filter.can_mask, gw_rcv, gwj); +} + +static int gw_notifier(struct notifier_block *nb, + unsigned long msg, void *data) +{ + struct net_device *dev = (struct net_device *)data; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) + if (!net_eq(dev_net(dev), &init_net)) + return NOTIFY_DONE; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if (dev->nd_net != &init_net) + return NOTIFY_DONE; +#endif + if (dev->type != ARPHRD_CAN) + return NOTIFY_DONE; + + if (msg == NETDEV_UNREGISTER) { + + struct gw_job *gwj = NULL; + struct hlist_node *n, *nx; + + spin_lock(&can_gw_list_lock); + + hlist_for_each_entry_safe(gwj, n, nx, &can_gw_list, list) { + + if (gwj->src_dev == dev || gwj->dst_dev == dev) { + hlist_del(&gwj->list); + can_gw_unregister_filter(gwj); + kfree(gwj); + } + } + + spin_unlock(&can_gw_list_lock); + } + + return NOTIFY_DONE; +} + +/* + * Dump information about all ports, in response to GETROUTE + */ +static int gw_dump_jobs(struct sk_buff *skb, struct netlink_callback *cb) +{ + printk(KERN_INFO "%s (TODO)\n", __FUNCTION__); + + return 0; +} + +static int gw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) +{ + + struct rtcanmsg *r; + struct nlattr *tb[CGW_MAX+1]; + struct gw_job *gwj; + u8 buf[CGW_MODATTR_LEN]; + int modidx = 0; + int err = 0; + + printk(KERN_INFO "%s: len %d attrlen %d\n", __FUNCTION__, + nlmsg_len(nlh), nlmsg_attrlen(nlh, sizeof(*r))); + + if (nlmsg_len(nlh) < sizeof(*r)) + return -EINVAL; + + r = nlmsg_data(nlh); + if (r->can_family != AF_CAN) + return -EPFNOSUPPORT; + + gwj = kmem_cache_alloc(gw_cache, GFP_KERNEL); + if (!gwj) + return -ENOMEM; + + gwj->src_dev = dev_get_by_index(&init_net, r->src_ifindex); + if (!gwj->src_dev) { + err = -ENODEV; + goto fail; + } + + /* for now the source device needs to be a CAN device */ + if (gwj->src_dev->type != ARPHRD_CAN) { + err = -ENODEV; + goto put_src_fail; + } + + gwj->dst_dev = dev_get_by_index(&init_net, r->dst_ifindex); + if (!gwj->dst_dev) { + err = -ENODEV; + goto put_src_fail; + } + + /* for now the destination device needs to be a CAN device */ + if (gwj->dst_dev->type != ARPHRD_CAN) { + err = -ENODEV; + goto put_src_dst_fail; + } + + gwj->flags = 0; + + if (r->can_txflags & CAN_GW_TXFLAGS_LOOPBACK) + gwj->flags |= CAN_TX_LOOPBACK; + + memset(&gwj->ccgw, 0, sizeof(gwj->ccgw)); + + /* check for additional attributes / filters here */ + + err = nlmsg_parse(nlh, sizeof(*r), tb, CGW_MAX, NULL); + if (err < 0) + goto put_src_dst_fail; + + /* check for can_filter in attributes */ + if (tb[CGW_FILTER] && + nla_len(tb[CGW_FILTER]) == sizeof(struct can_filter)) + nla_memcpy(&gwj->ccgw.filter, tb[CGW_FILTER], + sizeof(struct can_filter)); + + /* check for AND/OR/XOR/SET modifications */ + if (tb[CGW_MOD_AND] && + nla_len(tb[CGW_MOD_AND]) == CGW_MODATTR_LEN) { + nla_memcpy(&buf, tb[CGW_MOD_AND], CGW_MODATTR_LEN); + + memcpy(&gwj->ccgw.modframe.and, &buf[1], + sizeof(struct can_frame)); + + if (buf[0] & CGW_MOD_ID) + gwj->ccgw.modfunc[modidx++] = mod_and_id; + + if (buf[0] & CGW_MOD_DLC) + gwj->ccgw.modfunc[modidx++] = mod_and_dlc; + + if (buf[0] & CGW_MOD_DATA) + gwj->ccgw.modfunc[modidx++] = mod_and_data; + } + + if (tb[CGW_MOD_OR] && + nla_len(tb[CGW_MOD_OR]) == CGW_MODATTR_LEN) { + nla_memcpy(&buf, tb[CGW_MOD_OR], CGW_MODATTR_LEN); + + memcpy(&gwj->ccgw.modframe.or, &buf[1], + sizeof(struct can_frame)); + + if (buf[0] & CGW_MOD_ID) + gwj->ccgw.modfunc[modidx++] = mod_or_id; + + if (buf[0] & CGW_MOD_DLC) + gwj->ccgw.modfunc[modidx++] = mod_or_dlc; + + if (buf[0] & CGW_MOD_DATA) + gwj->ccgw.modfunc[modidx++] = mod_or_data; + } + + if (tb[CGW_MOD_XOR] && + nla_len(tb[CGW_MOD_XOR]) == CGW_MODATTR_LEN) { + nla_memcpy(&buf, tb[CGW_MOD_XOR], CGW_MODATTR_LEN); + + memcpy(&gwj->ccgw.modframe.xor, &buf[1], + sizeof(struct can_frame)); + + if (buf[0] & CGW_MOD_ID) + gwj->ccgw.modfunc[modidx++] = mod_xor_id; + + if (buf[0] & CGW_MOD_DLC) + gwj->ccgw.modfunc[modidx++] = mod_xor_dlc; + + if (buf[0] & CGW_MOD_DATA) + gwj->ccgw.modfunc[modidx++] = mod_xor_data; + } + + if (tb[CGW_MOD_SET] && + nla_len(tb[CGW_MOD_SET]) == CGW_MODATTR_LEN) { + nla_memcpy(&buf, tb[CGW_MOD_SET], CGW_MODATTR_LEN); + + memcpy(&gwj->ccgw.modframe.set, &buf[1], + sizeof(struct can_frame)); + + if (buf[0] & CGW_MOD_ID) + gwj->ccgw.modfunc[modidx++] = mod_set_id; + + if (buf[0] & CGW_MOD_DLC) + gwj->ccgw.modfunc[modidx++] = mod_set_dlc; + + if (buf[0] & CGW_MOD_DATA) + gwj->ccgw.modfunc[modidx++] = mod_set_data; + } + + spin_lock(&can_gw_list_lock); + + err = can_gw_register_filter(gwj); + if (!err) + hlist_add_head_rcu(&gwj->list, &can_gw_list); + + spin_unlock(&can_gw_list_lock); + + dev_put(gwj->src_dev); + dev_put(gwj->dst_dev); + + if (!err) + return 0; + +put_src_dst_fail: + dev_put(gwj->dst_dev); +put_src_fail: + dev_put(gwj->src_dev); +fail: + kmem_cache_free(gw_cache, gwj); + return err; +} + +static int gw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) +{ + printk(KERN_INFO "%s (TODO)\n", __FUNCTION__); + + return 0; +} + +static __init int gw_module_init(void) +{ + printk(banner); + + gw_cache = kmem_cache_create("can_gw", sizeof(struct gw_job), + 0, 0, NULL); + + if (!gw_cache) + return -ENOMEM; + + /* set notifier */ + notifier.notifier_call = gw_notifier; + register_netdevice_notifier(¬ifier); + + if (__rtnl_register(PF_CAN, RTM_GETROUTE, NULL, gw_dump_jobs)) { + unregister_netdevice_notifier(¬ifier); + kmem_cache_destroy(gw_cache); + return -ENOBUFS; + } + + /* Only the first call to __rtnl_register can fail */ + __rtnl_register(PF_CAN, RTM_NEWROUTE, gw_create_job, NULL); + __rtnl_register(PF_CAN, RTM_DELROUTE, gw_remove_job, NULL); + + return 0; +} + +static __exit void gw_module_exit(void) +{ + struct gw_job *gwj = NULL; + struct hlist_node *n, *nx; + + rtnl_unregister_all(PF_CAN); + + unregister_netdevice_notifier(¬ifier); + + spin_lock(&can_gw_list_lock); + + hlist_for_each_entry_safe(gwj, n, nx, &can_gw_list, list) { + hlist_del(&gwj->list); + can_gw_unregister_filter(gwj); + kfree(gwj); + } + + spin_unlock(&can_gw_list_lock); + + rcu_barrier(); /* Wait for completion of call_rcu()'s */ + + kmem_cache_destroy(gw_cache); +} + +module_init(gw_module_init); +module_exit(gw_module_exit); diff --git a/test/Makefile b/test/Makefile index 20e1c72..6dea626 100644 --- a/test/Makefile +++ b/test/Makefile @@ -65,6 +65,7 @@ PROGRAMS = tst-raw \ tst-bcm-tx-sendto \ tst-bcm-dump \ tst-proc \ + gwtest \ canecho all: $(PROGRAMS) diff --git a/test/gwtest.c b/test/gwtest.c new file mode 100644 index 0000000..283d420 --- /dev/null +++ b/test/gwtest.c @@ -0,0 +1,117 @@ +/* + * A real quick'n'dirty hack to add a netlink CAN gateway entry. + * + * Parts of this code were taken from the iproute source and the original + * vcan.c from Urs Thuermann. + * + * Oliver Hartkopp 2010-02-18 + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, "addattr_l: message exceeded bound of %d\n", + maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return 0; +} + +int main(int argc, char **argv) +{ + int s; + int err = 0; + + struct { + struct nlmsghdr n; + struct rtcanmsg r; + char buf[1000]; + + } req; + + static struct can_frame modframe; + struct can_filter filter; + struct sockaddr_nl nladdr; + char modbuf[CGW_MODATTR_LEN]; + + s = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtcanmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; + req.n.nlmsg_type = RTM_NEWROUTE; + req.n.nlmsg_seq = 0; + + req.r.can_family = AF_CAN; + req.r.src_ifindex = if_nametoindex("vcan2"); + req.r.dst_ifindex = if_nametoindex("vcan3"); + req.r.can_txflags = CAN_GW_TXFLAGS_LOOPBACK; + + /* add new attributes here */ + + filter.can_id = 0x400; + filter.can_mask = 0x700; + + addattr_l(&req.n, sizeof(req), CGW_FILTER, &filter, sizeof(filter)); + modframe.can_id = 0x555; + modframe.can_dlc = 5; + *(unsigned long long *)modframe.data = 0x5555555555555555ULL; + + modbuf[0] = CGW_MOD_ID; + memcpy(&modbuf[1], &modframe, sizeof(struct can_frame)); + + addattr_l(&req.n, sizeof(req), CGW_MOD_SET, modbuf, CGW_MODATTR_LEN); + + modbuf[0] = CGW_MOD_DLC; + memcpy(&modbuf[1], &modframe, sizeof(struct can_frame)); + + addattr_l(&req.n, sizeof(req), CGW_MOD_AND, modbuf, CGW_MODATTR_LEN); + + modbuf[0] = CGW_MOD_DATA; + memcpy(&modbuf[1], &modframe, sizeof(struct can_frame)); + + addattr_l(&req.n, sizeof(req), CGW_MOD_XOR, modbuf, CGW_MODATTR_LEN); + + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + err = sendto(s, &req, req.n.nlmsg_len, 0, + (struct sockaddr*)&nladdr, sizeof(nladdr)); + + perror("netlink says "); + close(s); + + return 0; +} + -- 2.39.2