// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) /* gw.c - CAN frame Gateway/Router/Bridge with netlink interface * * Copyright (c) 2019 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. *
*/
/* So far we just support CAN -> CAN routing and frame modifications. * * The internal can_can_gw structure contains data and attributes for * a CAN -> CAN gateway job.
*/ struct can_can_gw { struct can_filter filter; int src_idx; int dst_idx;
};
/* list entry for CAN gateways jobs */ struct cgw_job { struct hlist_node list; struct rcu_head rcu;
u32 handled_frames;
u32 dropped_frames;
u32 deleted_frames; struct cf_mod __rcu *cf_mod; union { /* CAN frame data source */ struct net_device *dev;
} src; union { /* CAN frame data destination */ struct net_device *dev;
} dst; union { struct can_can_gw ccgw; /* tbc */
};
u8 gwtype;
u8 limit_hops;
u16 flags;
};
/* modification functions that are invoked in the hot path in can_can_gw_rcv */
/* retrieve valid CC DLC value and store it into 'len' */ staticvoid mod_retrieve_ccdlc(struct canfd_frame *cf)
{ struct can_frame *ccf = (struct can_frame *)cf;
/* len8_dlc is only valid if len == CAN_MAX_DLEN */ if (ccf->len != CAN_MAX_DLEN) return;
/* do we have a valid len8_dlc value from 9 .. 15 ? */ if (ccf->len8_dlc > CAN_MAX_DLEN && ccf->len8_dlc <= CAN_MAX_RAW_DLC)
ccf->len = ccf->len8_dlc;
}
/* convert valid CC DLC value in 'len' into struct can_frame elements */ staticvoid mod_store_ccdlc(struct canfd_frame *cf)
{ struct can_frame *ccf = (struct can_frame *)cf;
staticvoid canframecpy(struct canfd_frame *dst, struct can_frame *src)
{ /* Copy the struct members separately to ensure that no uninitialized * data are copied in the 3 bytes hole of the struct. This is needed * to make easy compares of the data in the struct cf_mod.
*/
staticvoid canfdframecpy(struct canfd_frame *dst, struct canfd_frame *src)
{ /* Copy the struct members separately to ensure that no uninitialized * data are copied in the 2 bytes hole of the struct. This is needed * to make easy compares of the data in the struct cf_mod.
*/
staticint cgw_chk_csum_parms(s8 fr, s8 to, s8 re, struct rtcanmsg *r)
{
s8 dlen = CAN_MAX_DLEN;
if (r->flags & CGW_FLAGS_CAN_FD)
dlen = CANFD_MAX_DLEN;
/* absolute dlc values 0 .. 7 => 0 .. 7, e.g. data [0] * relative to received dlc -1 .. -8 : * e.g. for received dlc = 8 * -1 => index = 7 (data[7]) * -3 => index = 5 (data[5]) * -8 => index = 0 (data[0])
*/
if (fr >= -dlen && fr < dlen &&
to >= -dlen && to < dlen &&
re >= -dlen && re < dlen) return 0; else return -EINVAL;
}
staticinlineint calc_idx(int idx, int rx_len)
{ if (idx < 0) return rx_len + idx; else return idx;
}
staticvoid cgw_csum_xor_rel(struct canfd_frame *cf, struct cgw_csum_xor *xor)
{ int from = calc_idx(xor->from_idx, cf->len); int to = calc_idx(xor->to_idx, cf->len); int res = calc_idx(xor->result_idx, cf->len);
u8 val = xor->init_xor_val; int i;
if (from < 0 || to < 0 || res < 0) return;
if (from <= to) { for (i = from; i <= to; i++)
val ^= cf->data[i];
} else { for (i = from; i >= to; i--)
val ^= cf->data[i];
}
cf->data[res] = val;
}
staticvoid cgw_csum_xor_pos(struct canfd_frame *cf, struct cgw_csum_xor *xor)
{
u8 val = xor->init_xor_val; int i;
for (i = xor->from_idx; i <= xor->to_idx; i++)
val ^= cf->data[i];
cf->data[xor->result_idx] = val;
}
staticvoid cgw_csum_xor_neg(struct canfd_frame *cf, struct cgw_csum_xor *xor)
{
u8 val = xor->init_xor_val; int i;
for (i = xor->from_idx; i >= xor->to_idx; i--)
val ^= cf->data[i];
cf->data[xor->result_idx] = val;
}
staticvoid cgw_csum_crc8_rel(struct canfd_frame *cf, struct cgw_csum_crc8 *crc8)
{ int from = calc_idx(crc8->from_idx, cf->len); int to = calc_idx(crc8->to_idx, cf->len); int res = calc_idx(crc8->result_idx, cf->len);
u8 crc = crc8->init_crc_val; int i;
if (from < 0 || to < 0 || res < 0) return;
if (from <= to) { for (i = crc8->from_idx; i <= crc8->to_idx; i++)
crc = crc8->crctab[crc ^ cf->data[i]];
} else { for (i = crc8->from_idx; i >= crc8->to_idx; i--)
crc = crc8->crctab[crc ^ cf->data[i]];
}
/* the receive & process & send function */ staticvoid can_can_gw_rcv(struct sk_buff *skb, void *data)
{ struct cgw_job *gwj = (struct cgw_job *)data; struct canfd_frame *cf; struct sk_buff *nskb; struct cf_mod *mod; int modidx = 0;
/* process strictly Classic CAN or CAN FD frames */ if (gwj->flags & CGW_FLAGS_CAN_FD) { if (!can_is_canfd_skb(skb)) return;
} else { if (!can_is_can_skb(skb)) return;
}
/* Do not handle CAN frames routed more than 'max_hops' times. * In general we should never catch this delimiter which is intended * to cover a misconfiguration protection (e.g. circular CAN routes). * * The Controller Area Network controllers only accept CAN frames with * correct CRCs - which are not visible in the controller registers. * According to skbuff.h documentation the csum_start element for IP * checksums is undefined/unused when ip_summed == CHECKSUM_UNNECESSARY. * Only CAN skbs can be processed here which already have this property.
*/
#define cgw_hops(skb) ((skb)->csum_start)
BUG_ON(skb->ip_summed != CHECKSUM_UNNECESSARY);
if (cgw_hops(skb) >= max_hops) { /* indicate deleted frames due to misconfiguration */
gwj->deleted_frames++; return;
}
if (!(gwj->dst.dev->flags & IFF_UP)) {
gwj->dropped_frames++; return;
}
/* is sending the skb back to the incoming interface not allowed? */ if (!(gwj->flags & CGW_FLAGS_CAN_IIF_TX_OK) &&
can_skb_prv(skb)->ifindex == gwj->dst.dev->ifindex) return;
/* clone the given skb, which has not been done in can_rcv() * * When there is at least one modification function activated, * we need to copy the skb as we want to modify skb->data.
*/
mod = rcu_dereference(gwj->cf_mod); if (mod->modfunc[0])
nskb = skb_copy(skb, GFP_ATOMIC); else
nskb = skb_clone(skb, GFP_ATOMIC);
if (!nskb) {
gwj->dropped_frames++; return;
}
/* put the incremented hop counter in the cloned skb */
cgw_hops(nskb) = cgw_hops(skb) + 1;
/* first processing of this CAN frame -> adjust to private hop limit */ if (gwj->limit_hops && cgw_hops(nskb) == 1)
cgw_hops(nskb) = max_hops - gwj->limit_hops + 1;
nskb->dev = gwj->dst.dev;
/* pointer to modifiable CAN frame */
cf = (struct canfd_frame *)nskb->data;
/* perform preprocessed modification functions if there are any */ while (modidx < MAX_MODFUNCTIONS && mod->modfunc[modidx])
(*mod->modfunc[modidx++])(cf, mod);
/* Has the CAN frame been modified? */ if (modidx) { /* get available space for the processed CAN frame type */ int max_len = nskb->len - offsetof(struct canfd_frame, data);
/* dlc may have changed, make sure it fits to the CAN frame */ if (cf->len > max_len) { /* delete frame due to misconfiguration */
gwj->deleted_frames++;
kfree_skb(nskb); return;
}
/* check for checksum updates */ if (mod->csumfunc.crc8)
(*mod->csumfunc.crc8)(cf, &mod->csum.crc8);
if (mod->csumfunc.xor)
(*mod->csumfunc.xor)(cf, &mod->csum.xor);
}
/* clear the skb timestamp if not configured the other way */ if (!(gwj->flags & CGW_FLAGS_CAN_SRC_TSTAMP))
nskb->tstamp = 0;
/* send to netdevice */ if (can_send(nskb, gwj->flags & CGW_FLAGS_CAN_ECHO))
gwj->dropped_frames++; else
gwj->handled_frames++;
}
/* cgw_job::cf_mod is always accessed from the same cgw_job object within * the same RCU read section. Once cgw_job is scheduled for removal, * cf_mod can also be removed without mandating an additional grace period.
*/
kfree(rcu_access_pointer(gwj->cf_mod));
kmem_cache_free(cgw_cache, gwj);
}
/* Dump information about all CAN gateway jobs, in response to RTM_GETROUTE */ staticint cgw_dump_jobs(struct sk_buff *skb, struct netlink_callback *cb)
{ struct net *net = sock_net(skb->sk); struct cgw_job *gwj = NULL; int idx = 0; int s_idx = cb->args[0];
if (mb.modtype & CGW_MOD_ID)
mod->modfunc[modidx++] = mod_set_id;
if (mb.modtype & CGW_MOD_DLC)
mod->modfunc[modidx++] = mod_set_ccdlc;
if (mb.modtype & CGW_MOD_DATA)
mod->modfunc[modidx++] = mod_set_data;
}
}
/* check for checksum operations after CAN frame modifications */ if (modidx) { if (tb[CGW_CS_CRC8]) { struct cgw_csum_crc8 *c = nla_data(tb[CGW_CS_CRC8]);
err = cgw_chk_csum_parms(c->from_idx, c->to_idx,
c->result_idx, r); if (err) return err;
/* is sending the skb back to the incoming interface intended? */ if (gwj->src.dev == gwj->dst.dev &&
!(gwj->flags & CGW_FLAGS_CAN_IIF_TX_OK)) {
err = -EINVAL; goto out;
}
ASSERT_RTNL();
err = cgw_register_filter(net, gwj); if (!err)
hlist_add_head_rcu(&gwj->list, &net->can.cgw_list);
out: if (err) {
kmem_cache_free(cgw_cache, gwj);
out_free_cf:
kfree(mod);
} return err;
}
/* two interface indices both set to 0 => remove all entries */ if (!ccgw.src_idx && !ccgw.dst_idx) {
cgw_remove_all_jobs(net); return 0;
}
err = -EINVAL;
ASSERT_RTNL();
/* remove only the first matching entry */
hlist_for_each_entry_safe(gwj, nx, &net->can.cgw_list, list) { struct cf_mod *cf_mod;
if (gwj->flags != r->flags) continue;
if (gwj->limit_hops != limhops) continue;
cf_mod = cgw_job_cf_mod(gwj); /* we have a match when uid is enabled and identical */ if (cf_mod->uid || mod.uid) { if (cf_mod->uid != mod.uid) continue;
} else { /* no uid => check for identical modifications */ if (memcmp(cf_mod, &mod, sizeof(mod))) continue;
}
/* if (r->gwtype == CGW_TYPE_CAN_CAN) - is made sure here */ if (memcmp(&gwj->ccgw, &ccgw, sizeof(ccgw))) continue;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.