Skip to content

Commit

Permalink
ipv4: Early TCP socket demux.
Browse files Browse the repository at this point in the history
Input packet processing for local sockets involves two major demuxes.
One for the route and one for the socket.

But we can optimize this down to one demux for certain kinds of local
sockets.

Currently we only do this for established TCP sockets, but it could
at least in theory be expanded to other kinds of connections.

If a TCP socket is established then it's identity is fully specified.

This means that whatever input route was used during the three-way
handshake must work equally well for the rest of the connection since
the keys will not change.

Once we move to established state, we cache the receive packet's input
route to use later.

Like the existing cached route in sk->sk_dst_cache used for output
packets, we have to check for route invalidations using dst->obsolete
and dst->ops->check().

Early demux occurs outside of a socket locked section, so when a route
invalidation occurs we defer the fixup of sk->sk_rx_dst until we are
actually inside of established state packet processing and thus have
the socket locked.

Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
davem330 committed Jun 20, 2012
1 parent f9242b6 commit 41063e9
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 24 deletions.
4 changes: 2 additions & 2 deletions include/net/inet_hashtables.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,10 @@ static inline struct sock *__inet_lookup_skb(struct inet_hashinfo *hashinfo,
const __be16 sport,
const __be16 dport)
{
struct sock *sk;
struct sock *sk = skb_steal_sock(skb);
const struct iphdr *iph = ip_hdr(skb);

if (unlikely(sk = skb_steal_sock(skb)))
if (sk)
return sk;
else
return __inet_lookup(dev_net(skb_dst(skb)->dev), hashinfo,
Expand Down
1 change: 1 addition & 0 deletions include/net/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

/* This is used to register protocols. */
struct net_protocol {
int (*early_demux)(struct sk_buff *skb);
int (*handler)(struct sk_buff *skb);
void (*err_handler)(struct sk_buff *skb, u32 info);
int (*gso_send_check)(struct sk_buff *skb);
Expand Down
2 changes: 2 additions & 0 deletions include/net/sock.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ struct sock {
unsigned long sk_flags;
struct dst_entry *sk_dst_cache;
spinlock_t sk_dst_lock;
struct dst_entry *sk_rx_dst;
atomic_t sk_wmem_alloc;
atomic_t sk_omem_alloc;
int sk_sndbuf;
Expand Down Expand Up @@ -1426,6 +1427,7 @@ extern struct sk_buff *sock_rmalloc(struct sock *sk,
gfp_t priority);
extern void sock_wfree(struct sk_buff *skb);
extern void sock_rfree(struct sk_buff *skb);
extern void sock_edemux(struct sk_buff *skb);

extern int sock_setsockopt(struct socket *sock, int level,
int op, char __user *optval,
Expand Down
1 change: 1 addition & 0 deletions include/net/tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ extern void tcp_v4_err(struct sk_buff *skb, u32);

extern void tcp_shutdown (struct sock *sk, int how);

extern int tcp_v4_early_demux(struct sk_buff *skb);
extern int tcp_v4_rcv(struct sk_buff *skb);

extern struct inet_peer *tcp_v4_get_peer(struct sock *sk);
Expand Down
5 changes: 5 additions & 0 deletions net/core/sock.c
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,11 @@ void sock_rfree(struct sk_buff *skb)
}
EXPORT_SYMBOL(sock_rfree);

void sock_edemux(struct sk_buff *skb)
{
sock_put(skb->sk);
}
EXPORT_SYMBOL(sock_edemux);

int sock_i_uid(struct sock *sk)
{
Expand Down
18 changes: 10 additions & 8 deletions net/ipv4/af_inet.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ void inet_sock_destruct(struct sock *sk)

kfree(rcu_dereference_protected(inet->inet_opt, 1));
dst_release(rcu_dereference_check(sk->sk_dst_cache, 1));
dst_release(sk->sk_rx_dst);
sk_refcnt_debug_dec(sk);
}
EXPORT_SYMBOL(inet_sock_destruct);
Expand Down Expand Up @@ -1518,14 +1519,15 @@ static const struct net_protocol igmp_protocol = {
#endif

static const struct net_protocol tcp_protocol = {
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.gso_send_check = tcp_v4_gso_send_check,
.gso_segment = tcp_tso_segment,
.gro_receive = tcp4_gro_receive,
.gro_complete = tcp4_gro_complete,
.no_policy = 1,
.netns_ok = 1,
.early_demux = tcp_v4_early_demux,
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.gso_send_check = tcp_v4_gso_send_check,
.gso_segment = tcp_tso_segment,
.gro_receive = tcp4_gro_receive,
.gro_complete = tcp4_gro_complete,
.no_policy = 1,
.netns_ok = 1,
};

static const struct net_protocol udp_protocol = {
Expand Down
39 changes: 26 additions & 13 deletions net/ipv4/ip_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,19 +323,32 @@ static int ip_rcv_finish(struct sk_buff *skb)
* how the packet travels inside Linux networking.
*/
if (skb_dst(skb) == NULL) {
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph->tos, skb->dev);
if (unlikely(err)) {
if (err == -EHOSTUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),
IPSTATS_MIB_INADDRERRORS);
else if (err == -ENETUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),
IPSTATS_MIB_INNOROUTES);
else if (err == -EXDEV)
NET_INC_STATS_BH(dev_net(skb->dev),
LINUX_MIB_IPRPFILTER);
goto drop;
const struct net_protocol *ipprot;
int protocol = iph->protocol;
int err;

rcu_read_lock();
ipprot = rcu_dereference(inet_protos[protocol]);
err = -ENOENT;
if (ipprot && ipprot->early_demux)
err = ipprot->early_demux(skb);
rcu_read_unlock();

if (err) {
err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph->tos, skb->dev);
if (unlikely(err)) {
if (err == -EHOSTUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),
IPSTATS_MIB_INADDRERRORS);
else if (err == -ENETUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),
IPSTATS_MIB_INNOROUTES);
else if (err == -EXDEV)
NET_INC_STATS_BH(dev_net(skb->dev),
LINUX_MIB_IPRPFILTER);
goto drop;
}
}
}

Expand Down
16 changes: 15 additions & 1 deletion net/ipv4/tcp_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -5518,6 +5518,18 @@ int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
struct tcp_sock *tp = tcp_sk(sk);
int res;

if (sk->sk_rx_dst) {
struct dst_entry *dst = sk->sk_rx_dst;
if (unlikely(dst->obsolete)) {
if (dst->ops->check(dst, 0) == NULL) {
dst_release(dst);
sk->sk_rx_dst = NULL;
}
}
}
if (unlikely(sk->sk_rx_dst == NULL))
sk->sk_rx_dst = dst_clone(skb_dst(skb));

/*
* Header prediction.
* The code loosely follows the one in the famous
Expand Down Expand Up @@ -5729,8 +5741,10 @@ void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)

tcp_set_state(sk, TCP_ESTABLISHED);

if (skb != NULL)
if (skb != NULL) {
sk->sk_rx_dst = dst_clone(skb_dst(skb));
security_inet_conn_established(sk, skb);
}

/* Make sure socket is routed, for correct metrics. */
icsk->icsk_af_ops->rebuild_header(sk);
Expand Down
46 changes: 46 additions & 0 deletions net/ipv4/tcp_ipv4.c
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,52 @@ int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
}
EXPORT_SYMBOL(tcp_v4_do_rcv);

int tcp_v4_early_demux(struct sk_buff *skb)
{
struct net *net = dev_net(skb->dev);
const struct iphdr *iph;
const struct tcphdr *th;
struct sock *sk;
int err;

err = -ENOENT;
if (skb->pkt_type != PACKET_HOST)
goto out_err;

if (!pskb_may_pull(skb, ip_hdrlen(skb) + sizeof(struct tcphdr)))
goto out_err;

iph = ip_hdr(skb);
th = (struct tcphdr *) ((char *)iph + ip_hdrlen(skb));

if (th->doff < sizeof(struct tcphdr) / 4)
goto out_err;

if (!pskb_may_pull(skb, ip_hdrlen(skb) + th->doff * 4))
goto out_err;

sk = __inet_lookup_established(net, &tcp_hashinfo,
iph->saddr, th->source,
iph->daddr, th->dest,
skb->dev->ifindex);
if (sk) {
skb->sk = sk;
skb->destructor = sock_edemux;
if (sk->sk_state != TCP_TIME_WAIT) {
struct dst_entry *dst = sk->sk_rx_dst;
if (dst)
dst = dst_check(dst, 0);
if (dst) {
skb_dst_set_noref(skb, dst);
err = 0;
}
}
}

out_err:
return err;
}

/*
* From tcp_input.c
*/
Expand Down
2 changes: 2 additions & 0 deletions net/ipv4/tcp_minisocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ struct sock *tcp_create_openreq_child(struct sock *sk, struct request_sock *req,
struct tcp_sock *oldtp = tcp_sk(sk);
struct tcp_cookie_values *oldcvp = oldtp->cookie_values;

newsk->sk_rx_dst = dst_clone(skb_dst(skb));

/* TCP Cookie Transactions require space for the cookie pair,
* as it differs for each connection. There is no need to
* copy any s_data_payload stored at the original socket.
Expand Down

0 comments on commit 41063e9

Please sign in to comment.