netlink: add mmap'ed netlink helper functions
Add helper functions for looking up mmap'ed frame headers, reading and writing their status, allocating skbs with mmap'ed data areas and a poll function. Signed-off-by: Patrick McHardy <kaber@trash.net> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
ccdfcc3985
commit
9652e931e7
2 changed files with 190 additions and 2 deletions
|
@ -15,10 +15,17 @@ static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
|
||||||
return (struct nlmsghdr *)skb->data;
|
return (struct nlmsghdr *)skb->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum netlink_skb_flags {
|
||||||
|
NETLINK_SKB_MMAPED = 0x1, /* Packet data is mmaped */
|
||||||
|
NETLINK_SKB_TX = 0x2, /* Packet was sent by userspace */
|
||||||
|
NETLINK_SKB_DELIVERED = 0x4, /* Packet was delivered */
|
||||||
|
};
|
||||||
|
|
||||||
struct netlink_skb_parms {
|
struct netlink_skb_parms {
|
||||||
struct scm_creds creds; /* Skb credentials */
|
struct scm_creds creds; /* Skb credentials */
|
||||||
__u32 portid;
|
__u32 portid;
|
||||||
__u32 dst_group;
|
__u32 dst_group;
|
||||||
|
__u32 flags;
|
||||||
struct sock *sk;
|
struct sock *sk;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
#include <linux/audit.h>
|
#include <linux/audit.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/vmalloc.h>
|
#include <linux/vmalloc.h>
|
||||||
|
#include <asm/cacheflush.h>
|
||||||
|
|
||||||
#include <net/net_namespace.h>
|
#include <net/net_namespace.h>
|
||||||
#include <net/sock.h>
|
#include <net/sock.h>
|
||||||
|
@ -89,6 +90,7 @@ EXPORT_SYMBOL_GPL(nl_table);
|
||||||
static DECLARE_WAIT_QUEUE_HEAD(nl_table_wait);
|
static DECLARE_WAIT_QUEUE_HEAD(nl_table_wait);
|
||||||
|
|
||||||
static int netlink_dump(struct sock *sk);
|
static int netlink_dump(struct sock *sk);
|
||||||
|
static void netlink_skb_destructor(struct sk_buff *skb);
|
||||||
|
|
||||||
DEFINE_RWLOCK(nl_table_lock);
|
DEFINE_RWLOCK(nl_table_lock);
|
||||||
EXPORT_SYMBOL_GPL(nl_table_lock);
|
EXPORT_SYMBOL_GPL(nl_table_lock);
|
||||||
|
@ -109,6 +111,11 @@ static inline struct hlist_head *nl_portid_hashfn(struct nl_portid_hash *hash, u
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_NETLINK_MMAP
|
#ifdef CONFIG_NETLINK_MMAP
|
||||||
|
static bool netlink_skb_is_mmaped(const struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
return NETLINK_CB(skb).flags & NETLINK_SKB_MMAPED;
|
||||||
|
}
|
||||||
|
|
||||||
static __pure struct page *pgvec_to_page(const void *addr)
|
static __pure struct page *pgvec_to_page(const void *addr)
|
||||||
{
|
{
|
||||||
if (is_vmalloc_addr(addr))
|
if (is_vmalloc_addr(addr))
|
||||||
|
@ -332,8 +339,154 @@ out:
|
||||||
mutex_unlock(&nlk->pg_vec_lock);
|
mutex_unlock(&nlk->pg_vec_lock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void netlink_frame_flush_dcache(const struct nl_mmap_hdr *hdr)
|
||||||
|
{
|
||||||
|
#if ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE == 1
|
||||||
|
struct page *p_start, *p_end;
|
||||||
|
|
||||||
|
/* First page is flushed through netlink_{get,set}_status */
|
||||||
|
p_start = pgvec_to_page(hdr + PAGE_SIZE);
|
||||||
|
p_end = pgvec_to_page((void *)hdr + NL_MMAP_MSG_HDRLEN + hdr->nm_len - 1);
|
||||||
|
while (p_start <= p_end) {
|
||||||
|
flush_dcache_page(p_start);
|
||||||
|
p_start++;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum nl_mmap_status netlink_get_status(const struct nl_mmap_hdr *hdr)
|
||||||
|
{
|
||||||
|
smp_rmb();
|
||||||
|
flush_dcache_page(pgvec_to_page(hdr));
|
||||||
|
return hdr->nm_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void netlink_set_status(struct nl_mmap_hdr *hdr,
|
||||||
|
enum nl_mmap_status status)
|
||||||
|
{
|
||||||
|
hdr->nm_status = status;
|
||||||
|
flush_dcache_page(pgvec_to_page(hdr));
|
||||||
|
smp_wmb();
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct nl_mmap_hdr *
|
||||||
|
__netlink_lookup_frame(const struct netlink_ring *ring, unsigned int pos)
|
||||||
|
{
|
||||||
|
unsigned int pg_vec_pos, frame_off;
|
||||||
|
|
||||||
|
pg_vec_pos = pos / ring->frames_per_block;
|
||||||
|
frame_off = pos % ring->frames_per_block;
|
||||||
|
|
||||||
|
return ring->pg_vec[pg_vec_pos] + (frame_off * ring->frame_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct nl_mmap_hdr *
|
||||||
|
netlink_lookup_frame(const struct netlink_ring *ring, unsigned int pos,
|
||||||
|
enum nl_mmap_status status)
|
||||||
|
{
|
||||||
|
struct nl_mmap_hdr *hdr;
|
||||||
|
|
||||||
|
hdr = __netlink_lookup_frame(ring, pos);
|
||||||
|
if (netlink_get_status(hdr) != status)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return hdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct nl_mmap_hdr *
|
||||||
|
netlink_current_frame(const struct netlink_ring *ring,
|
||||||
|
enum nl_mmap_status status)
|
||||||
|
{
|
||||||
|
return netlink_lookup_frame(ring, ring->head, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct nl_mmap_hdr *
|
||||||
|
netlink_previous_frame(const struct netlink_ring *ring,
|
||||||
|
enum nl_mmap_status status)
|
||||||
|
{
|
||||||
|
unsigned int prev;
|
||||||
|
|
||||||
|
prev = ring->head ? ring->head - 1 : ring->frame_max;
|
||||||
|
return netlink_lookup_frame(ring, prev, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void netlink_increment_head(struct netlink_ring *ring)
|
||||||
|
{
|
||||||
|
ring->head = ring->head != ring->frame_max ? ring->head + 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void netlink_forward_ring(struct netlink_ring *ring)
|
||||||
|
{
|
||||||
|
unsigned int head = ring->head, pos = head;
|
||||||
|
const struct nl_mmap_hdr *hdr;
|
||||||
|
|
||||||
|
do {
|
||||||
|
hdr = __netlink_lookup_frame(ring, pos);
|
||||||
|
if (hdr->nm_status == NL_MMAP_STATUS_UNUSED)
|
||||||
|
break;
|
||||||
|
if (hdr->nm_status != NL_MMAP_STATUS_SKIP)
|
||||||
|
break;
|
||||||
|
netlink_increment_head(ring);
|
||||||
|
} while (ring->head != head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int netlink_poll(struct file *file, struct socket *sock,
|
||||||
|
poll_table *wait)
|
||||||
|
{
|
||||||
|
struct sock *sk = sock->sk;
|
||||||
|
struct netlink_sock *nlk = nlk_sk(sk);
|
||||||
|
unsigned int mask;
|
||||||
|
|
||||||
|
mask = datagram_poll(file, sock, wait);
|
||||||
|
|
||||||
|
spin_lock_bh(&sk->sk_receive_queue.lock);
|
||||||
|
if (nlk->rx_ring.pg_vec) {
|
||||||
|
netlink_forward_ring(&nlk->rx_ring);
|
||||||
|
if (!netlink_previous_frame(&nlk->rx_ring, NL_MMAP_STATUS_UNUSED))
|
||||||
|
mask |= POLLIN | POLLRDNORM;
|
||||||
|
}
|
||||||
|
spin_unlock_bh(&sk->sk_receive_queue.lock);
|
||||||
|
|
||||||
|
spin_lock_bh(&sk->sk_write_queue.lock);
|
||||||
|
if (nlk->tx_ring.pg_vec) {
|
||||||
|
if (netlink_current_frame(&nlk->tx_ring, NL_MMAP_STATUS_UNUSED))
|
||||||
|
mask |= POLLOUT | POLLWRNORM;
|
||||||
|
}
|
||||||
|
spin_unlock_bh(&sk->sk_write_queue.lock);
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct nl_mmap_hdr *netlink_mmap_hdr(struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
return (struct nl_mmap_hdr *)(skb->head - NL_MMAP_HDRLEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void netlink_ring_setup_skb(struct sk_buff *skb, struct sock *sk,
|
||||||
|
struct netlink_ring *ring,
|
||||||
|
struct nl_mmap_hdr *hdr)
|
||||||
|
{
|
||||||
|
unsigned int size;
|
||||||
|
void *data;
|
||||||
|
|
||||||
|
size = ring->frame_size - NL_MMAP_HDRLEN;
|
||||||
|
data = (void *)hdr + NL_MMAP_HDRLEN;
|
||||||
|
|
||||||
|
skb->head = data;
|
||||||
|
skb->data = data;
|
||||||
|
skb_reset_tail_pointer(skb);
|
||||||
|
skb->end = skb->tail + size;
|
||||||
|
skb->len = 0;
|
||||||
|
|
||||||
|
skb->destructor = netlink_skb_destructor;
|
||||||
|
NETLINK_CB(skb).flags |= NETLINK_SKB_MMAPED;
|
||||||
|
NETLINK_CB(skb).sk = sk;
|
||||||
|
}
|
||||||
#else /* CONFIG_NETLINK_MMAP */
|
#else /* CONFIG_NETLINK_MMAP */
|
||||||
|
#define netlink_skb_is_mmaped(skb) false
|
||||||
#define netlink_mmap sock_no_mmap
|
#define netlink_mmap sock_no_mmap
|
||||||
|
#define netlink_poll datagram_poll
|
||||||
#endif /* CONFIG_NETLINK_MMAP */
|
#endif /* CONFIG_NETLINK_MMAP */
|
||||||
|
|
||||||
static void netlink_destroy_callback(struct netlink_callback *cb)
|
static void netlink_destroy_callback(struct netlink_callback *cb)
|
||||||
|
@ -350,7 +503,35 @@ static void netlink_consume_callback(struct netlink_callback *cb)
|
||||||
|
|
||||||
static void netlink_skb_destructor(struct sk_buff *skb)
|
static void netlink_skb_destructor(struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
sock_rfree(skb);
|
#ifdef CONFIG_NETLINK_MMAP
|
||||||
|
struct nl_mmap_hdr *hdr;
|
||||||
|
struct netlink_ring *ring;
|
||||||
|
struct sock *sk;
|
||||||
|
|
||||||
|
/* If a packet from the kernel to userspace was freed because of an
|
||||||
|
* error without being delivered to userspace, the kernel must reset
|
||||||
|
* the status. In the direction userspace to kernel, the status is
|
||||||
|
* always reset here after the packet was processed and freed.
|
||||||
|
*/
|
||||||
|
if (netlink_skb_is_mmaped(skb)) {
|
||||||
|
hdr = netlink_mmap_hdr(skb);
|
||||||
|
sk = NETLINK_CB(skb).sk;
|
||||||
|
|
||||||
|
if (!(NETLINK_CB(skb).flags & NETLINK_SKB_DELIVERED)) {
|
||||||
|
hdr->nm_len = 0;
|
||||||
|
netlink_set_status(hdr, NL_MMAP_STATUS_VALID);
|
||||||
|
}
|
||||||
|
ring = &nlk_sk(sk)->rx_ring;
|
||||||
|
|
||||||
|
WARN_ON(atomic_read(&ring->pending) == 0);
|
||||||
|
atomic_dec(&ring->pending);
|
||||||
|
sock_put(sk);
|
||||||
|
|
||||||
|
skb->data = NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (skb->sk != NULL)
|
||||||
|
sock_rfree(skb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void netlink_skb_set_owner_r(struct sk_buff *skb, struct sock *sk)
|
static void netlink_skb_set_owner_r(struct sk_buff *skb, struct sock *sk)
|
||||||
|
@ -2349,7 +2530,7 @@ static const struct proto_ops netlink_ops = {
|
||||||
.socketpair = sock_no_socketpair,
|
.socketpair = sock_no_socketpair,
|
||||||
.accept = sock_no_accept,
|
.accept = sock_no_accept,
|
||||||
.getname = netlink_getname,
|
.getname = netlink_getname,
|
||||||
.poll = datagram_poll,
|
.poll = netlink_poll,
|
||||||
.ioctl = sock_no_ioctl,
|
.ioctl = sock_no_ioctl,
|
||||||
.listen = sock_no_listen,
|
.listen = sock_no_listen,
|
||||||
.shutdown = sock_no_shutdown,
|
.shutdown = sock_no_shutdown,
|
||||||
|
|
Loading…
Add table
Reference in a new issue