Commit 1bca1320 authored by Bart De Schuymer's avatar Bart De Schuymer Committed by David S. Miller

[BRIDGE]: Deal with non-linear SKBs in ebtables.

parent 7768d03b
......@@ -34,7 +34,7 @@ static inline int should_deliver(const struct net_bridge_port *p,
int br_dev_queue_push_xmit(struct sk_buff *skb)
{
#ifdef CONFIG_NETFILTER
/* FIXME: skb bas not been linearized: is this valid?? --RR */
/* ip_refrag calls ip_fragment, which doesn't copy the MAC header. */
if (skb->nf_bridge)
memcpy(skb->data - 16, skb->nf_bridge->hh, 16);
#endif
......
......@@ -468,8 +468,7 @@ static unsigned int br_nf_post_routing(unsigned int hook, struct sk_buff **pskb,
struct sk_buff *skb = *pskb;
struct nf_bridge_info *nf_bridge = (*pskb)->nf_bridge;
/* FIXME: skb as not been linearized. Is this still true? --RR */
/* Be very paranoid. */
/* Be very paranoid. Must be a device driver bug. */
if (skb->mac.raw < skb->head || skb->mac.raw + ETH_HLEN > skb->data) {
printk(KERN_CRIT "br_netfilter: Argh!! br_nf_post_routing: "
"bad mac.raw pointer.");
......
......@@ -19,89 +19,72 @@ static int ebt_filter_arp(const struct sk_buff *skb, const struct net_device *in
const struct net_device *out, const void *data, unsigned int datalen)
{
struct ebt_arp_info *info = (struct ebt_arp_info *)data;
struct arphdr arph;
if (skb_copy_bits(skb, 0, &arph, sizeof(arph)))
return EBT_NOMATCH;
if (info->bitmask & EBT_ARP_OPCODE && FWINV(info->opcode !=
((*skb).nh.arph)->ar_op, EBT_ARP_OPCODE))
arph.ar_op, EBT_ARP_OPCODE))
return EBT_NOMATCH;
if (info->bitmask & EBT_ARP_HTYPE && FWINV(info->htype !=
((*skb).nh.arph)->ar_hrd, EBT_ARP_HTYPE))
arph.ar_hrd, EBT_ARP_HTYPE))
return EBT_NOMATCH;
if (info->bitmask & EBT_ARP_PTYPE && FWINV(info->ptype !=
((*skb).nh.arph)->ar_pro, EBT_ARP_PTYPE))
arph.ar_pro, EBT_ARP_PTYPE))
return EBT_NOMATCH;
if (info->bitmask & (EBT_ARP_SRC_IP | EBT_ARP_DST_IP))
{
uint32_t arp_len = sizeof(struct arphdr) +
(2 * (((*skb).nh.arph)->ar_hln)) +
(2 * (((*skb).nh.arph)->ar_pln));
uint32_t dst;
uint32_t src;
if (info->bitmask & (EBT_ARP_SRC_IP | EBT_ARP_DST_IP)) {
uint32_t addr;
/* Make sure the packet is long enough */
if ((((*skb).nh.raw) + arp_len) > (*skb).tail)
return EBT_NOMATCH;
/* IPv4 addresses are always 4 bytes */
if (((*skb).nh.arph)->ar_pln != sizeof(uint32_t))
if (arph.ar_pln != sizeof(uint32_t))
return EBT_NOMATCH;
if (info->bitmask & EBT_ARP_SRC_IP) {
memcpy(&src, ((*skb).nh.raw) + sizeof(struct arphdr) +
((*skb).nh.arph)->ar_hln, sizeof(uint32_t));
if (FWINV(info->saddr != (src & info->smsk),
if (skb_copy_bits(skb, sizeof(struct arphdr) +
arph.ar_hln, &addr, sizeof(addr)))
return EBT_NOMATCH;
if (FWINV(info->saddr != (addr & info->smsk),
EBT_ARP_SRC_IP))
return EBT_NOMATCH;
}
if (info->bitmask & EBT_ARP_DST_IP) {
memcpy(&dst, ((*skb).nh.raw)+sizeof(struct arphdr) +
(2*(((*skb).nh.arph)->ar_hln)) +
(((*skb).nh.arph)->ar_pln), sizeof(uint32_t));
if (FWINV(info->daddr != (dst & info->dmsk),
if (skb_copy_bits(skb, sizeof(struct arphdr) +
2*arph.ar_hln + sizeof(uint32_t), &addr,
sizeof(addr)))
return EBT_NOMATCH;
if (FWINV(info->daddr != (addr & info->dmsk),
EBT_ARP_DST_IP))
return EBT_NOMATCH;
}
}
if (info->bitmask & (EBT_ARP_SRC_MAC | EBT_ARP_DST_MAC))
{
uint32_t arp_len = sizeof(struct arphdr) +
(2 * (((*skb).nh.arph)->ar_hln)) +
(2 * (((*skb).nh.arph)->ar_pln));
unsigned char dst[ETH_ALEN];
unsigned char src[ETH_ALEN];
if (info->bitmask & (EBT_ARP_SRC_MAC | EBT_ARP_DST_MAC)) {
unsigned char mac[ETH_ALEN];
uint8_t verdict, i;
/* Make sure the packet is long enough */
if ((((*skb).nh.raw) + arp_len) > (*skb).tail)
return EBT_NOMATCH;
/* MAC addresses are 6 bytes */
if (((*skb).nh.arph)->ar_hln != ETH_ALEN)
if (arph.ar_hln != ETH_ALEN)
return EBT_NOMATCH;
if (info->bitmask & EBT_ARP_SRC_MAC) {
uint8_t verdict, i;
memcpy(&src, ((*skb).nh.raw) +
sizeof(struct arphdr),
ETH_ALEN);
if (skb_copy_bits(skb, sizeof(struct arphdr), &mac,
ETH_ALEN))
return EBT_NOMATCH;
verdict = 0;
for (i = 0; i < 6; i++)
verdict |= (src[i] ^ info->smaddr[i]) &
verdict |= (mac[i] ^ info->smaddr[i]) &
info->smmsk[i];
if (FWINV(verdict != 0, EBT_ARP_SRC_MAC))
return EBT_NOMATCH;
}
if (info->bitmask & EBT_ARP_DST_MAC) {
uint8_t verdict, i;
memcpy(&dst, ((*skb).nh.raw) +
sizeof(struct arphdr) +
(((*skb).nh.arph)->ar_hln) +
(((*skb).nh.arph)->ar_pln),
ETH_ALEN);
if (skb_copy_bits(skb, sizeof(struct arphdr) +
arph.ar_hln + arph.ar_pln, &mac, ETH_ALEN))
return EBT_NOMATCH;
verdict = 0;
for (i = 0; i < 6; i++)
verdict |= (dst[i] ^ info->dmaddr[i]) &
verdict |= (mac[i] ^ info->dmaddr[i]) &
info->dmmsk[i];
if (FWINV(verdict != 0, EBT_ARP_DST_MAC))
return EBT_NOMATCH;
......
......@@ -23,53 +23,50 @@ struct tcpudphdr {
uint16_t dst;
};
union h_u {
unsigned char *raw;
struct tcpudphdr *tuh;
};
static int ebt_filter_ip(const struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, const void *data,
unsigned int datalen)
{
struct ebt_ip_info *info = (struct ebt_ip_info *)data;
union {struct iphdr iph; struct tcpudphdr ports;} u;
if (skb_copy_bits(skb, 0, &u.iph, sizeof(u.iph)))
return EBT_NOMATCH;
if (info->bitmask & EBT_IP_TOS &&
FWINV(info->tos != ((*skb).nh.iph)->tos, EBT_IP_TOS))
FWINV(info->tos != u.iph.tos, EBT_IP_TOS))
return EBT_NOMATCH;
if (info->bitmask & EBT_IP_SOURCE &&
FWINV((u.iph.saddr & info->smsk) !=
info->saddr, EBT_IP_SOURCE))
return EBT_NOMATCH;
if ((info->bitmask & EBT_IP_DEST) &&
FWINV((u.iph.daddr & info->dmsk) !=
info->daddr, EBT_IP_DEST))
return EBT_NOMATCH;
if (info->bitmask & EBT_IP_PROTO) {
if (FWINV(info->protocol != ((*skb).nh.iph)->protocol,
EBT_IP_PROTO))
if (FWINV(info->protocol != u.iph.protocol, EBT_IP_PROTO))
return EBT_NOMATCH;
if (!(info->bitmask & EBT_IP_DPORT) &&
!(info->bitmask & EBT_IP_SPORT))
return EBT_MATCH;
if (skb_copy_bits(skb, u.iph.ihl*4, &u.ports,
sizeof(u.ports)))
return EBT_NOMATCH;
if ( info->protocol == IPPROTO_TCP ||
info->protocol == IPPROTO_UDP )
{
union h_u h;
h.raw = skb->data + skb->nh.iph->ihl*4;
if (info->bitmask & EBT_IP_DPORT) {
uint16_t port = ntohs(h.tuh->dst);
if (FWINV(port < info->dport[0] ||
port > info->dport[1],
u.ports.dst = ntohs(u.ports.dst);
if (FWINV(u.ports.dst < info->dport[0] ||
u.ports.dst > info->dport[1],
EBT_IP_DPORT))
return EBT_NOMATCH;
}
if (info->bitmask & EBT_IP_SPORT) {
uint16_t port = ntohs(h.tuh->src);
if (FWINV(port < info->sport[0] ||
port > info->sport[1],
u.ports.src = ntohs(u.ports.src);
if (FWINV(u.ports.src < info->sport[0] ||
u.ports.src > info->sport[1],
EBT_IP_SPORT))
return EBT_NOMATCH;
}
}
}
if (info->bitmask & EBT_IP_SOURCE &&
FWINV((((*skb).nh.iph)->saddr & info->smsk) !=
info->saddr, EBT_IP_SOURCE))
return EBT_NOMATCH;
if ((info->bitmask & EBT_IP_DEST) &&
FWINV((((*skb).nh.iph)->daddr & info->dmsk) !=
info->daddr, EBT_IP_DEST))
return EBT_NOMATCH;
return EBT_MATCH;
}
......@@ -86,7 +83,7 @@ static int ebt_ip_check(const char *tablename, unsigned int hookmask,
if (info->bitmask & ~EBT_IP_MASK || info->invflags & ~EBT_IP_MASK)
return -EINVAL;
if (info->bitmask & (EBT_IP_DPORT | EBT_IP_SPORT)) {
if (info->bitmask & EBT_IPROTO)
if (info->invflags & EBT_IP_PROTO)
return -EINVAL;
if (info->protocol != IPPROTO_TCP &&
info->protocol != IPPROTO_UDP)
......
......@@ -32,48 +32,105 @@ static int ebt_log_check(const char *tablename, unsigned int hookmask,
return 0;
}
struct tcpudphdr
{
uint16_t src;
uint16_t dst;
};
struct arppayload
{
unsigned char mac_src[ETH_ALEN];
unsigned char ip_src[4];
unsigned char mac_dst[ETH_ALEN];
unsigned char ip_dst[4];
};
static void print_MAC(unsigned char *p)
{
int i;
for (i = 0; i < ETH_ALEN; i++, p++)
printk("%02x%c", *p, i == ETH_ALEN - 1 ? ' ':':');
}
#define myNIPQUAD(a) a[0], a[1], a[2], a[3]
static void ebt_log(const struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, const void *data, unsigned int datalen)
{
struct ebt_log_info *info = (struct ebt_log_info *)data;
char level_string[4] = "< >";
level_string[1] = '0' + info->loglevel;
union {struct iphdr iph; struct tcpudphdr ports;
struct arphdr arph; struct arppayload arpp;} u;
level_string[1] = '0' + info->loglevel;
spin_lock_bh(&ebt_log_lock);
printk(level_string);
printk("%s IN=%s OUT=%s ", info->prefix, in ? in->name : "",
out ? out->name : "");
if (skb->dev->hard_header_len) {
int i;
unsigned char *p = (skb->mac.ethernet)->h_source;
printk("MAC source = ");
for (i = 0; i < ETH_ALEN; i++,p++)
printk("%02x%c", *p, i == ETH_ALEN - 1 ? ' ':':');
print_MAC((skb->mac.ethernet)->h_source);
printk("MAC dest = ");
p = (skb->mac.ethernet)->h_dest;
for (i = 0; i < ETH_ALEN; i++,p++)
printk("%02x%c", *p, i == ETH_ALEN - 1 ? ' ':':');
}
print_MAC((skb->mac.ethernet)->h_dest);
printk("proto = 0x%04x", ntohs(((*skb).mac.ethernet)->h_proto));
if ((info->bitmask & EBT_LOG_IP) && skb->mac.ethernet->h_proto ==
htons(ETH_P_IP)){
struct iphdr *iph = skb->nh.iph;
if (skb_copy_bits(skb, 0, &u.iph, sizeof(u.iph))) {
printk(" INCOMPLETE IP header");
goto out;
}
printk(" IP SRC=%u.%u.%u.%u IP DST=%u.%u.%u.%u,",
NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
printk(" IP tos=0x%02X, IP proto=%d", iph->tos, iph->protocol);
NIPQUAD(u.iph.saddr), NIPQUAD(u.iph.daddr));
printk(" IP tos=0x%02X, IP proto=%d", u.iph.tos,
u.iph.protocol);
if (u.iph.protocol == IPPROTO_TCP ||
u.iph.protocol == IPPROTO_UDP) {
if (skb_copy_bits(skb, u.iph.ihl*4, &u.ports,
sizeof(u.ports))) {
printk(" INCOMPLETE TCP/UDP header");
goto out;
}
printk(" SPT=%u DPT=%u", ntohs(u.ports.src),
ntohs(u.ports.dst));
}
goto out;
}
if ((info->bitmask & EBT_LOG_ARP) &&
((skb->mac.ethernet->h_proto == __constant_htons(ETH_P_ARP)) ||
(skb->mac.ethernet->h_proto == __constant_htons(ETH_P_RARP)))) {
struct arphdr * arph = skb->nh.arph;
if (skb_copy_bits(skb, 0, &u.arph, sizeof(u.arph))) {
printk(" INCOMPLETE ARP header");
goto out;
}
printk(" ARP HTYPE=%d, PTYPE=0x%04x, OPCODE=%d",
ntohs(arph->ar_hrd), ntohs(arph->ar_pro),
ntohs(arph->ar_op));
ntohs(u.arph.ar_hrd), ntohs(u.arph.ar_pro),
ntohs(u.arph.ar_op));
/* If it's for Ethernet and the lengths are OK,
* then log the ARP payload */
if (u.arph.ar_hrd == __constant_htons(1) &&
u.arph.ar_hln == ETH_ALEN &&
u.arph.ar_pln == sizeof(uint32_t)) {
if (skb_copy_bits(skb, sizeof(u.arph), &u.arpp,
sizeof(u.arpp))) {
printk(" INCOMPLETE ARP payload");
goto out;
}
printk(" ARP MAC SRC=");
print_MAC(u.arpp.mac_src);
printk(" ARP IP SRC=%u.%u.%u.%u",
myNIPQUAD(u.arpp.ip_src));
printk(" ARP MAC DST=");
print_MAC(u.arpp.mac_dst);
printk(" ARP IP DST=%u.%u.%u.%u",
myNIPQUAD(u.arpp.ip_dst));
}
}
out:
printk("\n");
spin_unlock_bh(&ebt_log_lock);
}
......
......@@ -39,92 +39,53 @@ MODULE_LICENSE("GPL");
#define INV_FLAG(_inv_flag_) (info->invflags & _inv_flag_) ? "!" : ""
#define GET_BITMASK(_BIT_MASK_) info->bitmask & _BIT_MASK_
#define SET_BITMASK(_BIT_MASK_) info->bitmask |= _BIT_MASK_
#define EXIT_ON_MISMATCH(_MATCH_,_MASK_) if (!((info->_MATCH_ == _MATCH_)^!!(info->invflags & _MASK_))) return 1;
#define EXIT_ON_MISMATCH(_MATCH_,_MASK_) {if (!((info->_MATCH_ == _MATCH_)^!!(info->invflags & _MASK_))) return EBT_NOMATCH;}
/*
* Function description: ebt_filter_vlan() is main engine for
* checking passed 802.1Q frame according to
* the passed extension parameters (in the *data buffer)
* ebt_filter_vlan() is called after successful check the rule params
* by ebt_check_vlan() function.
* Parameters:
* const struct sk_buff *skb - pointer to passed ethernet frame buffer
* const void *data - pointer to passed extension parameters
* unsigned int datalen - length of passed *data buffer
* const struct net_device *in -
* const struct net_device *out -
* const struct ebt_counter *c -
* Returned values:
* 0 - ok (all rule params matched)
* 1 - miss (rule params not acceptable to the parsed frame)
*/
static int
ebt_filter_vlan(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *data, unsigned int datalen)
{
struct ebt_vlan_info *info = (struct ebt_vlan_info *) data; /* userspace data */
struct vlan_ethhdr *frame = (struct vlan_ethhdr *) skb->mac.raw; /* Passed tagged frame */
struct ebt_vlan_info *info = (struct ebt_vlan_info *) data;
struct vlan_ethhdr frame;
unsigned short TCI; /* Whole TCI, given from parsed frame */
unsigned short id; /* VLAN ID, given from frame TCI */
unsigned char prio; /* user_priority, given from frame TCI */
unsigned short encap; /* VLAN encapsulated Type/Length field, given from orig frame */
/* VLAN encapsulated Type/Length field, given from orig frame */
unsigned short encap;
if (skb_copy_bits(skb, 0, &frame, sizeof(frame)))
return EBT_NOMATCH;
/*
* Tag Control Information (TCI) consists of the following elements:
/* Tag Control Information (TCI) consists of the following elements:
* - User_priority. The user_priority field is three bits in length,
* interpreted as a binary number.
* - Canonical Format Indicator (CFI). The Canonical Format Indicator
* (CFI) is a single bit flag value. Currently ignored.
* - VLAN Identifier (VID). The VID is encoded as
* an unsigned binary number.
*/
TCI = ntohs(frame->h_vlan_TCI);
* an unsigned binary number. */
TCI = ntohs(frame.h_vlan_TCI);
id = TCI & VLAN_VID_MASK;
prio = (TCI >> 13) & 0x7;
encap = frame->h_vlan_encapsulated_proto;
encap = frame.h_vlan_encapsulated_proto;
/*
* Checking VLAN Identifier (VID)
*/
if (GET_BITMASK(EBT_VLAN_ID)) { /* Is VLAN ID parsed? */
/* Checking VLAN Identifier (VID) */
if (GET_BITMASK(EBT_VLAN_ID))
EXIT_ON_MISMATCH(id, EBT_VLAN_ID);
}
/*
* Checking user_priority
*/
if (GET_BITMASK(EBT_VLAN_PRIO)) { /* Is VLAN user_priority parsed? */
/* Checking user_priority */
if (GET_BITMASK(EBT_VLAN_PRIO))
EXIT_ON_MISMATCH(prio, EBT_VLAN_PRIO);
}
/*
* Checking Encapsulated Proto (Length/Type) field
*/
if (GET_BITMASK(EBT_VLAN_ENCAP)) { /* Is VLAN Encap parsed? */
/* Checking Encapsulated Proto (Length/Type) field */
if (GET_BITMASK(EBT_VLAN_ENCAP))
EXIT_ON_MISMATCH(encap, EBT_VLAN_ENCAP);
}
/*
* All possible extension parameters was parsed.
* If rule never returned by missmatch, then all ok.
*/
return 0;
return EBT_MATCH;
}
/*
* Function description: ebt_vlan_check() is called when userspace
* delivers the table entry to the kernel,
* and to check that userspace doesn't give a bad table.
* Parameters:
* const char *tablename - table name string
* unsigned int hooknr - hook number
* const struct ebt_entry *e - ebtables entry basic set
* const void *data - pointer to passed extension parameters
* unsigned int datalen - length of passed *data buffer
* Returned values:
* 0 - ok (all delivered rule params are correct)
* 1 - miss (rule params is out of range, invalid, incompatible, etc.)
*/
static int
ebt_check_vlan(const char *tablename,
unsigned int hooknr,
......@@ -132,9 +93,7 @@ ebt_check_vlan(const char *tablename,
{
struct ebt_vlan_info *info = (struct ebt_vlan_info *) data;
/*
* Parameters buffer overflow check
*/
/* Parameters buffer overflow check */
if (datalen != sizeof(struct ebt_vlan_info)) {
DEBUG_MSG
("passed size %d is not eq to ebt_vlan_info (%Zd)\n",
......@@ -142,9 +101,7 @@ ebt_check_vlan(const char *tablename,
return -EINVAL;
}
/*
* Is it 802.1Q frame checked?
*/
/* Is it 802.1Q frame checked? */
if (e->ethproto != __constant_htons(ETH_P_8021Q)) {
DEBUG_MSG
("passed entry proto %2.4X is not 802.1Q (8100)\n",
......@@ -152,34 +109,28 @@ ebt_check_vlan(const char *tablename,
return -EINVAL;
}
/*
* Check for bitmask range
* True if even one bit is out of mask
*/
/* Check for bitmask range
* True if even one bit is out of mask */
if (info->bitmask & ~EBT_VLAN_MASK) {
DEBUG_MSG("bitmask %2X is out of mask (%2X)\n",
info->bitmask, EBT_VLAN_MASK);
return -EINVAL;
}
/*
* Check for inversion flags range
*/
/* Check for inversion flags range */
if (info->invflags & ~EBT_VLAN_MASK) {
DEBUG_MSG("inversion flags %2X is out of mask (%2X)\n",
info->invflags, EBT_VLAN_MASK);
return -EINVAL;
}
/*
* Reserved VLAN ID (VID) values
/* Reserved VLAN ID (VID) values
* -----------------------------
* 0 - The null VLAN ID.
* 1 - The default Port VID (PVID)
* 0x0FFF - Reserved for implementation use.
* if_vlan.h: VLAN_GROUP_ARRAY_LEN 4096.
*/
if (GET_BITMASK(EBT_VLAN_ID)) { /* when vlan-id param was spec-ed */
* if_vlan.h: VLAN_GROUP_ARRAY_LEN 4096. */
if (GET_BITMASK(EBT_VLAN_ID)) {
if (!!info->id) { /* if id!=0 => check vid range */
if (info->id > VLAN_GROUP_ARRAY_LEN) {
DEBUG_MSG
......@@ -187,32 +138,25 @@ ebt_check_vlan(const char *tablename,
info->id);
return -EINVAL;
}
/*
* Note: This is valid VLAN-tagged frame point.
/* Note: This is valid VLAN-tagged frame point.
* Any value of user_priority are acceptable,
* but should be ignored according to 802.1Q Std.
* So we just drop the prio flag.
*/
* So we just drop the prio flag. */
info->bitmask &= ~EBT_VLAN_PRIO;
}
/*
* Else, id=0 (null VLAN ID) => user_priority range (any?)
*/
/* Else, id=0 (null VLAN ID) => user_priority range (any?) */
}
if (GET_BITMASK(EBT_VLAN_PRIO)) {
if ((unsigned char) info->prio > 7) {
DEBUG_MSG
("prio %d is out of range (0-7)\n",
DEBUG_MSG("prio %d is out of range (0-7)\n",
info->prio);
return -EINVAL;
}
}
/*
* Check for encapsulated proto range - it is possible to be
/* Check for encapsulated proto range - it is possible to be
* any value for u_short range.
* if_ether.h: ETH_ZLEN 60 - Min. octets in frame sans FCS
*/
* if_ether.h: ETH_ZLEN 60 - Min. octets in frame sans FCS */
if (GET_BITMASK(EBT_VLAN_ENCAP)) {
if ((unsigned short) ntohs(info->encap) < ETH_ZLEN) {
DEBUG_MSG
......@@ -232,9 +176,6 @@ static struct ebt_match filter_vlan = {
.me = THIS_MODULE,
};
/*
* Module initialization function.
*/
static int __init init(void)
{
DEBUG_MSG("ebtables 802.1Q extension module v"
......@@ -243,9 +184,6 @@ static int __init init(void)
return ebt_register_match(&filter_vlan);
}
/*
* Module "finalization" function
*/
static void __exit fini(void)
{
ebt_unregister_match(&filter_vlan);
......
......@@ -175,10 +175,6 @@ unsigned int ebt_do_table (unsigned int hook, struct sk_buff **pskb,
char *base;
struct ebt_table_info *private = table->private;
/* FIXME: Push down to extensions --RR */
if (skb_is_nonlinear(*pskb) && skb_linearize(*pskb, GFP_ATOMIC) != 0)
return NF_DROP;
read_lock_bh(&table->lock);
cb_base = COUNTER_BASE(private->counters, private->nentries,
smp_processor_id());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment