* Re: [PATCH v7] rps: Receive Packet Steering
From: Eric Dumazet @ 2010-03-17 14:09 UTC (permalink / raw)
To: Changli Gao; +Cc: David Miller, therbert, netdev
In-Reply-To: <412e6f7f1003170059r1f0fa4cfrbe8b3f22102ee9d9@mail.gmail.com>
Le mercredi 17 mars 2010 à 15:59 +0800, Changli Gao a écrit :
> On Wed, Mar 17, 2010 at 3:07 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> > Le mercredi 17 mars 2010 à 09:54 +0800, Changli Gao a écrit :
> >> On Wed, Mar 17, 2010 at 5:13 AM, David Miller <davem@davemloft.net> wrote:
> >> >
> >> > I'll integrate this as soon as I open up net-next-2.6
> >>
> >> It is really a good news. Now linux also can dispatch packets as
> >> FreeBSD does via netisr. Can we walk farer, and support weighted
> >> distribution?
> >>
> >
> > May I ask why ? What would be the goal ?
> >
>
> For example, I have a firewall with dual core CPU, and use core 0 for
> IRQ and dispatching. If I use both core 0 and core 1 for the left
> processing, core 0 will be overloaded, and if I use core 1 for the
> left processing, core 0 will be light load. In order to take full of
> advantage of hardware, I need weighted the packet distribution.
I would not use RPS at all, weighted or not, unless your firewall must
perform heavy duty work (l7 or complex rules)
If the firewall setup is expensive, then IRQ processing has minor cost,
and RPS fits the bill (cpu handling IRQ will be a bit more loaded than
its buddy).
RPS is a win when _some_ TCP/UDP processing occurs, and we try to do
this processing on a cpu that will also run user space thread with data
in cpu cache (if process scheduler does a good job)
Not much applicable for routers...
Anyway, current sysfs RPS interface exposes
a /sys/class/net/eth0/queues/rx-0/rps_cpus bitmap,
I guess we could expose another file,
/sys/class/net/eth0/queues/rx-0/rps_map
to give different weight to cpus :
echo "0 1 0 1 0 1 1 1 1 1" >/sys/class/net/eth0/queues/rx-0/rps_map
cpu0 would get 30% of the postprocessing load, cpu1 70%
Using /sys/class/net/eth0/queues/rx-0/rps_cpus interface would give an
equal weight to each cpu :
# echo "0 1 0 1 0 1 1 1 1 1" >/sys/class/net/eth0/queues/rx-0/rps_map
# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
3
# cat /sys/class/net/eth0/queues/rx-0/rps_map
0 1 0 1 0 1 1 1 1 1
# echo 3 >/sys/class/net/eth0/queues/rx-0/rps_cpus
# cat /sys/class/net/eth0/queues/rx-0/rps_map
0 1
^ permalink raw reply
* Re: [PATCH] xfrm: cache bundle lookup results in flow cache
From: Timo Teräs @ 2010-03-17 14:16 UTC (permalink / raw)
To: Herbert Xu; +Cc: netdev
In-Reply-To: <20100317130704.GA2601@gondor.apana.org.au>
Herbert Xu wrote:
> On Mon, Mar 15, 2010 at 02:20:10PM +0200, Timo Teras wrote:
>> Instead of doing O(n) xfrm_find_bundle() call per-packet, cache
>> the previous lookup results in flow cache. The flow cache is
>> updated to be per-netns and more generic.
>
> This only works well if the traffic doesn't switch bundles much.
> But if that were the case then the number of bundles is likely
> to be small anyway.
The problem is if I have multipoint gre1 and policy that says
"encrypt all gre in transport mode".
Thus for each public address, I get one bundle. But the
xfrm_lookup() is called for each packet because ipgre_tunnel_xmit()
calls ip_route_output_key() on per-packet basis.
For my use-case it makes a huge difference.
> IOW I think if we're doing this then we should go the whole
> distance and directly cache bundles instead of policies in the
> flow cache.
Then we cannot maintain policy use time. But if it's not a
requirement, we could drop the policy from cache.
Also. With this and your recent flowi patch, I'm seeing pmtu
issues. Seems like xfrm_bundle_ok uses the original dst which
resulted in the creation of the bundle. Somehow that dst
does not get updated with pmtu... but the new dst used in
next xfrm_lookup for same target does have proper mtu.
I'm debugging right now why this is happening. Any ideas?
- Timo
^ permalink raw reply
* [PATCH 0/1] 3c59x: Acquire vortex lock instead of disabling irq
From: Chase Douglas @ 2010-03-17 14:33 UTC (permalink / raw)
To: netdev
I believe an Ubuntu user has found an issue that has been resolved in the -rt
patchset for a few years. I pulled the fix from the patchset and built a test
kernel for the user, but I've yet to hear usable results back (test kernel
isn't booting right, but it may be due to other big changes that went in the
newer Ubuntu kernel I gave him). In the meantime, I'm sending this on for folks
to take a look at. All useful information can be found in the commit message,
including a link to the bug report with a BUG message.
-- Chase
^ permalink raw reply
* [PATCH 1/1] 3c59x: Acquire vortex lock instead of disabling irq
From: Chase Douglas @ 2010-03-17 14:33 UTC (permalink / raw)
To: netdev
In-Reply-To: <1268836396-23943-1-git-send-email-chase.douglas@canonical.com>
Last year, threaded IRQ handlers were introduced to the mainline kernel.
This change requires the disable_irq function to sleep if any IRQ
handler threads for a given IRQ line are running.
Back in 2006, while working on the -rt patch set that had threaded IRQ
handlers, the vortex_timer function was causing scheduling bugs because
it is run in softirq context and called disable_irq. This patch was the
best fix determined at the time, and still exists in the .33 -rt
patchset. Now that threaded IRQ handlers are present in the mainline
kernel we need to apply the patch there as well.
http://lkml.org/lkml/2006/5/12/178
BugLink: http://bugs.launchpad.net/bugs/533335
Signed-off-by: Chase Douglas <chase.douglas@canonical.com>
---
drivers/net/3c59x.c | 5 +++--
1 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/net/3c59x.c b/drivers/net/3c59x.c
index f965431..bdaff0f 100644
--- a/drivers/net/3c59x.c
+++ b/drivers/net/3c59x.c
@@ -1764,6 +1764,7 @@ vortex_timer(unsigned long data)
int next_tick = 60*HZ;
int ok = 0;
int media_status, old_window;
+ unsigned long flags;
if (vortex_debug > 2) {
pr_debug("%s: Media selection timer tick happened, %s.\n",
@@ -1771,7 +1772,7 @@ vortex_timer(unsigned long data)
pr_debug("dev->watchdog_timeo=%d\n", dev->watchdog_timeo);
}
- disable_irq_lockdep(dev->irq);
+ spin_lock_irqsave(&vp->lock, flags);
old_window = ioread16(ioaddr + EL3_CMD) >> 13;
EL3WINDOW(4);
media_status = ioread16(ioaddr + Wn4_Media);
@@ -1851,7 +1852,7 @@ leave_media_alone:
dev->name, media_tbl[dev->if_port].name);
EL3WINDOW(old_window);
- enable_irq_lockdep(dev->irq);
+ spin_unlock_irqrestore(&vp->lock, flags);
mod_timer(&vp->timer, RUN_AT(next_tick));
if (vp->deferred)
iowrite16(FakeIntr, ioaddr + EL3_CMD);
--
1.6.3.3
^ permalink raw reply related
* [PATCH] vhost: fix error handling in vring ioctls
From: Michael S. Tsirkin @ 2010-03-17 14:42 UTC (permalink / raw)
To: Michael S. Tsirkin, Jiri Slaby, kvm, virtualization, netdev,
linux-kernel
Stanse found a locking problem in vhost_set_vring:
several returns from VHOST_SET_VRING_KICK, VHOST_SET_VRING_CALL,
VHOST_SET_VRING_ERR with the vq->mutex held.
Fix these up.
Reported-by: Jiri Slaby <jirislaby@gmail.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
---
drivers/vhost/vhost.c | 18 ++++++++++++------
1 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c
index 7cd55e0..7bd7a1e 100644
--- a/drivers/vhost/vhost.c
+++ b/drivers/vhost/vhost.c
@@ -476,8 +476,10 @@ static long vhost_set_vring(struct vhost_dev *d, int ioctl, void __user *argp)
if (r < 0)
break;
eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd);
- if (IS_ERR(eventfp))
- return PTR_ERR(eventfp);
+ if (IS_ERR(eventfp)) {
+ r = PTR_ERR(eventfp);
+ break;
+ }
if (eventfp != vq->kick) {
pollstop = filep = vq->kick;
pollstart = vq->kick = eventfp;
@@ -489,8 +491,10 @@ static long vhost_set_vring(struct vhost_dev *d, int ioctl, void __user *argp)
if (r < 0)
break;
eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd);
- if (IS_ERR(eventfp))
- return PTR_ERR(eventfp);
+ if (IS_ERR(eventfp)) {
+ r = PTR_ERR(eventfp);
+ break;
+ }
if (eventfp != vq->call) {
filep = vq->call;
ctx = vq->call_ctx;
@@ -505,8 +509,10 @@ static long vhost_set_vring(struct vhost_dev *d, int ioctl, void __user *argp)
if (r < 0)
break;
eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd);
- if (IS_ERR(eventfp))
- return PTR_ERR(eventfp);
+ if (IS_ERR(eventfp)) {
+ r = PTR_ERR(eventfp);
+ break;
+ }
if (eventfp != vq->error) {
filep = vq->error;
vq->error = eventfp;
--
1.7.0.18.g0d53a5
^ permalink raw reply related
* [PATCH] vhost: fix interrupt mitigation with raw sockets
From: Michael S. Tsirkin @ 2010-03-17 14:43 UTC (permalink / raw)
To: Michael S. Tsirkin, Juan Quintela, Unai Uribarri, kvm,
virtualization, netdev
A thinko in code means we never trigger interrupt
mitigation. Fix this.
Reported-by: Juan Quintela <quintela@redhat.com>
Reported-by: Unai Uribarri <unai.uribarri@optenet.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
---
drivers/vhost/net.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
index fcafb6b..a6a88df 100644
--- a/drivers/vhost/net.c
+++ b/drivers/vhost/net.c
@@ -125,7 +125,7 @@ static void handle_tx(struct vhost_net *net)
mutex_lock(&vq->mutex);
vhost_disable_notify(vq);
- if (wmem < sock->sk->sk_sndbuf * 2)
+ if (wmem < sock->sk->sk_sndbuf / 2)
tx_poll_stop(net);
hdr_size = vq->hdr_size;
--
1.7.0.18.g0d53a5
^ permalink raw reply related
* Re: [PATCH] xfrm: cache bundle lookup results in flow cache
From: Herbert Xu @ 2010-03-17 14:58 UTC (permalink / raw)
To: Timo Teräs; +Cc: netdev
In-Reply-To: <4BA0E435.6090801@iki.fi>
On Wed, Mar 17, 2010 at 04:16:21PM +0200, Timo Teräs wrote:
>
> The problem is if I have multipoint gre1 and policy that says
> "encrypt all gre in transport mode".
>
> Thus for each public address, I get one bundle. But the
> xfrm_lookup() is called for each packet because ipgre_tunnel_xmit()
> calls ip_route_output_key() on per-packet basis.
>
> For my use-case it makes a huge difference.
But if your traffic switches between those tunnels on each packet,
we're back to square one, right?
> Then we cannot maintain policy use time. But if it's not a
> requirement, we could drop the policy from cache.
I don't see why we can't maintain the policy use time if we did
this, all you need is a back-pointer from the top xfrm_dst.
> Also. With this and your recent flowi patch, I'm seeing pmtu
> issues. Seems like xfrm_bundle_ok uses the original dst which
> resulted in the creation of the bundle. Somehow that dst
> does not get updated with pmtu... but the new dst used in
> next xfrm_lookup for same target does have proper mtu.
> I'm debugging right now why this is happening. Any ideas?
The dynamic MTU is always maintained in a normal dst object in
the IPv4 routing cache. Each xfrm_dst points to such a dst
through xdst->route.
If you were looking at the xfrm_dst's own MTU then that may well
cause problems.
Cheers,
--
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
^ permalink raw reply
* Re: [PATCH v7] rps: Receive Packet Steering
From: Tom Herbert @ 2010-03-17 15:01 UTC (permalink / raw)
To: Eric Dumazet; +Cc: Changli Gao, David Miller, netdev
In-Reply-To: <1268834957.2899.352.camel@edumazet-laptop>
On Wed, Mar 17, 2010 at 7:09 AM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> Le mercredi 17 mars 2010 à 15:59 +0800, Changli Gao a écrit :
>> On Wed, Mar 17, 2010 at 3:07 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
>> > Le mercredi 17 mars 2010 à 09:54 +0800, Changli Gao a écrit :
>> >> On Wed, Mar 17, 2010 at 5:13 AM, David Miller <davem@davemloft.net> wrote:
>> >> >
>> >> > I'll integrate this as soon as I open up net-next-2.6
>> >>
>> >> It is really a good news. Now linux also can dispatch packets as
>> >> FreeBSD does via netisr. Can we walk farer, and support weighted
>> >> distribution?
>> >>
>> >
>> > May I ask why ? What would be the goal ?
>> >
>>
>> For example, I have a firewall with dual core CPU, and use core 0 for
>> IRQ and dispatching. If I use both core 0 and core 1 for the left
>> processing, core 0 will be overloaded, and if I use core 1 for the
>> left processing, core 0 will be light load. In order to take full of
>> advantage of hardware, I need weighted the packet distribution.
>
> I would not use RPS at all, weighted or not, unless your firewall must
> perform heavy duty work (l7 or complex rules)
>
> If the firewall setup is expensive, then IRQ processing has minor cost,
> and RPS fits the bill (cpu handling IRQ will be a bit more loaded than
> its buddy).
>
> RPS is a win when _some_ TCP/UDP processing occurs, and we try to do
> this processing on a cpu that will also run user space thread with data
> in cpu cache (if process scheduler does a good job)
> Not much applicable for routers...
>
>
> Anyway, current sysfs RPS interface exposes
> a /sys/class/net/eth0/queues/rx-0/rps_cpus bitmap,
>
> I guess we could expose another file,
> /sys/class/net/eth0/queues/rx-0/rps_map
> to give different weight to cpus :
>
> echo "0 1 0 1 0 1 1 1 1 1" >/sys/class/net/eth0/queues/rx-0/rps_map
>
> cpu0 would get 30% of the postprocessing load, cpu1 70%
>
> Using /sys/class/net/eth0/queues/rx-0/rps_cpus interface would give an
> equal weight to each cpu :
>
>
> # echo "0 1 0 1 0 1 1 1 1 1" >/sys/class/net/eth0/queues/rx-0/rps_map
> # cat /sys/class/net/eth0/queues/rx-0/rps_cpus
> 3
> # cat /sys/class/net/eth0/queues/rx-0/rps_map
> 0 1 0 1 0 1 1 1 1 1
> # echo 3 >/sys/class/net/eth0/queues/rx-0/rps_cpus
> # cat /sys/class/net/eth0/queues/rx-0/rps_map
> 0 1
Alternatively, the rps_map could be specified explicitly, which will
allow weighting. For example "0 0 0 0 2 10 10 10" would select CPUs
0, 2, 10 for the map with weights four, one, and three respectively.
This would go back to have sysfs files with multiple values in them,
so it might not be the right interface.
Tom
^ permalink raw reply
* Re: [PATCH 1/3] netlink: fix NETLINK_RECV_NO_ENOBUFS in netlink_set_err()
From: Patrick McHardy @ 2010-03-17 15:26 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netdev, davem
In-Reply-To: <20100316232957.4185.46217.stgit@decadence>
Pablo Neira Ayuso wrote:
> Currently, ENOBUFS errors are reported to the socket via
> netlink_set_err() even if NETLINK_RECV_NO_ENOBUFS is set. However,
> that should not happen. This fixes this problem and it changes the
> prototype of netlink_set_err() to return the number of sockets whose
> error has been set. This allows to know if any error has been set.
> This return value is used in the next patch in these bugfix series.
But that only happens if we have a message allocate error, which is
a different situation than rcvqueue overrun, which I thought the
original patch was supposed to handle (disable netlink congestion
control).
Is there any problem with these errors?
^ permalink raw reply
* Re: [PATCH v7] rps: Receive Packet Steering
From: Eric Dumazet @ 2010-03-17 15:34 UTC (permalink / raw)
To: Tom Herbert; +Cc: Changli Gao, David Miller, netdev
In-Reply-To: <65634d661003170801x1042a6am563c9d937ba672a4@mail.gmail.com>
Le mercredi 17 mars 2010 à 08:01 -0700, Tom Herbert a écrit :
> On Wed, Mar 17, 2010 at 7:09 AM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> > Anyway, current sysfs RPS interface exposes
> > a /sys/class/net/eth0/queues/rx-0/rps_cpus bitmap,
> >
> > I guess we could expose another file,
> > /sys/class/net/eth0/queues/rx-0/rps_map
> > to give different weight to cpus :
> >
> > echo "0 1 0 1 0 1 1 1 1 1" >/sys/class/net/eth0/queues/rx-0/rps_map
> >
> > cpu0 would get 30% of the postprocessing load, cpu1 70%
> >
> > Using /sys/class/net/eth0/queues/rx-0/rps_cpus interface would give an
> > equal weight to each cpu :
> >
> >
> > # echo "0 1 0 1 0 1 1 1 1 1" >/sys/class/net/eth0/queues/rx-0/rps_map
> > # cat /sys/class/net/eth0/queues/rx-0/rps_cpus
> > 3
> > # cat /sys/class/net/eth0/queues/rx-0/rps_map
> > 0 1 0 1 0 1 1 1 1 1
> > # echo 3 >/sys/class/net/eth0/queues/rx-0/rps_cpus
> > # cat /sys/class/net/eth0/queues/rx-0/rps_map
> > 0 1
>
> Alternatively, the rps_map could be specified explicitly, which will
> allow weighting. For example "0 0 0 0 2 10 10 10" would select CPUs
> 0, 2, 10 for the map with weights four, one, and three respectively.
> This would go back to have sysfs files with multiple values in them,
> so it might not be the right interface.
>
Well, you describe same idea... being able to do
echo "0 1 0 1 0 1 1 1 1 1" >rps_map
or
echo "0 0 0 1 1 1 1 1 1 1" >rps_map
or
echo "0 1 1 0 1 1 0 1 1 1" >rps_map
(filling the real map[] with given cpu numbers)
I was interleaving my cpus because I found it cool :)
^ permalink raw reply
* [PATCH RFC] tun: add ioctl to modify vnet header size
From: Michael S. Tsirkin @ 2010-03-17 15:45 UTC (permalink / raw)
To: David Stevens
Cc: David S. Miller, Herbert Xu, Michael S. Tsirkin, Paul Moore,
David Woodhouse, Sridhar Samudrala, netdev, linux-kernel
virtio added mergeable buffers mode where 2 bytes of extra info is put
after vnet header but before actual data (tun does not need this data).
In hindsight, it would have been better to add the new info *before* the
packet: as it is, users need a lot of tricky code to skip the extra 2
bytes in the middle of the iovec, and in fact applications seem to get
it wrong, and only work with specific iovec layout. The fact we might
need to split iovec also means we might in theory overflow iovec max
size.
This patch adds a simpler way for applications to handle this,
and future proofs the interface against further extensions,
by making the size of the virtio net header configurable
from userspace. As a result, tun driver will simply
skip the extra 2 bytes on both input and output.
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
---
drivers/net/tun.c | 31 +++++++++++++++++++++++++++----
include/linux/if_tun.h | 2 ++
2 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index ce1efa4..0b11222 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -110,6 +110,8 @@ struct tun_struct {
struct tap_filter txflt;
struct socket socket;
+ int vnet_hdr_sz;
+
#ifdef TUN_DEBUG
int debug;
#endif
@@ -559,7 +561,7 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun,
}
if (tun->flags & TUN_VNET_HDR) {
- if ((len -= sizeof(gso)) > count)
+ if ((len -= tun->vnet_hdr_sz) > count)
return -EINVAL;
if (memcpy_fromiovecend((void *)&gso, iv, offset, sizeof(gso)))
@@ -571,7 +573,7 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun,
if (gso.hdr_len > len)
return -EINVAL;
- offset += sizeof(gso);
+ offset += tun->vnet_hdr_sz;
}
if ((tun->flags & TUN_TYPE_MASK) == TUN_TAP_DEV) {
@@ -714,7 +716,7 @@ static __inline__ ssize_t tun_put_user(struct tun_struct *tun,
if (tun->flags & TUN_VNET_HDR) {
struct virtio_net_hdr gso = { 0 }; /* no info leak */
- if ((len -= sizeof(gso)) < 0)
+ if ((len -= tun->vnet_hdr_sz) < 0)
return -EINVAL;
if (skb_is_gso(skb)) {
@@ -745,7 +747,7 @@ static __inline__ ssize_t tun_put_user(struct tun_struct *tun,
if (unlikely(memcpy_toiovecend(iv, (void *)&gso, total,
sizeof(gso))))
return -EFAULT;
- total += sizeof(gso);
+ total += tun->vnet_hdr_sz;
}
len = min_t(int, skb->len, len);
@@ -1029,6 +1031,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
tun->dev = dev;
tun->flags = flags;
tun->txflt.count = 0;
+ tun->vnet_hdr_sz = sizeof(struct virtio_net_hdr);
err = -ENOMEM;
sk = sk_alloc(net, AF_UNSPEC, GFP_KERNEL, &tun_proto);
@@ -1170,6 +1173,7 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
struct sock_fprog fprog;
struct ifreq ifr;
int sndbuf;
+ int vnet_hdr_sz;
int ret;
if (cmd == TUNSETIFF || _IOC_TYPE(cmd) == 0x89)
@@ -1315,6 +1319,25 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
tun->socket.sk->sk_sndbuf = sndbuf;
break;
+ case TUNGETVNETHDRSZ:
+ vnet_hdr_sz = tun->vnet_hdr_sz;
+ if (copy_to_user(argp, &vnet_hdr_sz, sizeof(vnet_hdr_sz)))
+ ret = -EFAULT;
+ break;
+
+ case TUNSETVNETHDRSZ:
+ if (copy_from_user(&vnet_hdr_sz, argp, sizeof(vnet_hdr_sz))) {
+ ret = -EFAULT;
+ break;
+ }
+ if (vnet_hdr_sz < (int)sizeof(struct virtio_net_hdr)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ tun->vnet_hdr_sz = vnet_hdr_sz;
+ break;
+
case TUNATTACHFILTER:
/* Can be set only for TAPs */
ret = -EINVAL;
diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h
index 1350a24..06b1829 100644
--- a/include/linux/if_tun.h
+++ b/include/linux/if_tun.h
@@ -51,6 +51,8 @@
#define TUNSETSNDBUF _IOW('T', 212, int)
#define TUNATTACHFILTER _IOW('T', 213, struct sock_fprog)
#define TUNDETACHFILTER _IOW('T', 214, struct sock_fprog)
+#define TUNGETVNETHDRSZ _IOR('T', 215, int)
+#define TUNSETVNETHDRSZ _IOW('T', 216, int)
/* TUNSETIFF ifr flags */
#define IFF_TUN 0x0001
--
1.7.0.18.g0d53a5
^ permalink raw reply related
* Re: [PATCH] xfrm: cache bundle lookup results in flow cache
From: Timo Teräs @ 2010-03-17 15:56 UTC (permalink / raw)
To: Herbert Xu; +Cc: netdev
In-Reply-To: <20100317145850.GA4257@gondor.apana.org.au>
Herbert Xu wrote:
> On Wed, Mar 17, 2010 at 04:16:21PM +0200, Timo Teräs wrote:
>> The problem is if I have multipoint gre1 and policy that says
>> "encrypt all gre in transport mode".
>>
>> Thus for each public address, I get one bundle. But the
>> xfrm_lookup() is called for each packet because ipgre_tunnel_xmit()
>> calls ip_route_output_key() on per-packet basis.
>>
>> For my use-case it makes a huge difference.
>
> But if your traffic switches between those tunnels on each packet,
> we're back to square one, right?
Not to my understanding. Why would it change?
>> Then we cannot maintain policy use time. But if it's not a
>> requirement, we could drop the policy from cache.
>
> I don't see why we can't maintain the policy use time if we did
> this, all you need is a back-pointer from the top xfrm_dst.
Sure.
>> Also. With this and your recent flowi patch, I'm seeing pmtu
>> issues. Seems like xfrm_bundle_ok uses the original dst which
>> resulted in the creation of the bundle. Somehow that dst
>> does not get updated with pmtu... but the new dst used in
>> next xfrm_lookup for same target does have proper mtu.
>> I'm debugging right now why this is happening. Any ideas?
>
> The dynamic MTU is always maintained in a normal dst object in
> the IPv4 routing cache. Each xfrm_dst points to such a dst
> through xdst->route.
>
> If you were looking at the xfrm_dst's own MTU then that may well
> cause problems.
I figured the root cause. The original dst gets expired
rt_genid goes old. But xfrm_dst does not notice that so it
won't create a new bundle. xfrm_bundle_ok calls dst_check,
but dst->obsolete = 0, and ipv4_dst_check is a no-op anyway.
Somehow the rtable object should be able to tell back to
xfrm that the dst is not good anymore. Any ideas?
- Timo
^ permalink raw reply
* [PATCH] net: ipmr/ip6mr: fix potential out-of-bounds vif_table access
From: Patrick McHardy @ 2010-03-17 16:00 UTC (permalink / raw)
To: David S. Miller; +Cc: Linux Netdev List
[-- Attachment #1: Type: text/plain, Size: 0 bytes --]
[-- Attachment #2: 01.diff --]
[-- Type: text/x-patch, Size: 1616 bytes --]
commit 65782983286f43e2b14eec2d7287171a9957a754
Author: Patrick McHardy <kaber@trash.net>
Date: Wed Mar 17 16:54:20 2010 +0100
net: ipmr/ip6mr: fix potential out-of-bounds vif_table access
mfc_parent of cache entries is used to index into the vif_table and is
initialised from mfcctl->mfcc_parent. This can take values of to 2^16-1,
while the vif_table has only MAXVIFS (32) entries. The same problem
affects ip6mr.
Refuse invalid values to fix a potential out-of-bounds access. Unlike
the other validity checks, this is checked in ipmr_mfc_add() instead of
the setsockopt handler since its unused in the delete path and might be
uninitialized.
Signed-off-by: Patrick McHardy <kaber@trash.net>
diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index 8582e12..0b9d03c 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -802,6 +802,9 @@ static int ipmr_mfc_add(struct net *net, struct mfcctl *mfc, int mrtsock)
int line;
struct mfc_cache *uc, *c, **cp;
+ if (mfc->mfcc_parent >= MAXVIFS)
+ return -ENFILE;
+
line = MFC_HASH(mfc->mfcc_mcastgrp.s_addr, mfc->mfcc_origin.s_addr);
for (cp = &net->ipv4.mfc_cache_array[line];
diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c
index 52e0f74..23e4ac0 100644
--- a/net/ipv6/ip6mr.c
+++ b/net/ipv6/ip6mr.c
@@ -1113,6 +1113,9 @@ static int ip6mr_mfc_add(struct net *net, struct mf6cctl *mfc, int mrtsock)
unsigned char ttls[MAXMIFS];
int i;
+ if (mfc->mf6cc_parent >= MAXMIFS)
+ return -ENFILE;
+
memset(ttls, 255, MAXMIFS);
for (i = 0; i < MAXMIFS; i++) {
if (IF_ISSET(i, &mfc->mf6cc_ifset))
^ permalink raw reply related
* Re: [PATCH] net: ipmr/ip6mr: fix potential out-of-bounds vif_table access
From: Patrick McHardy @ 2010-03-17 16:04 UTC (permalink / raw)
To: David S. Miller; +Cc: Linux Netdev List
In-Reply-To: <4BA0FCB2.7010009@trash.net>
[-- Attachment #1: Type: text/plain, Size: 77 bytes --]
My appologies for using git-show again. Correctly formatted
patch attached.
[-- Attachment #2: 01.diff --]
[-- Type: text/x-patch, Size: 1496 bytes --]
mfc_parent of cache entries is used to index into the vif_table and is
initialised from mfcctl->mfcc_parent. This can take values of to 2^16-1,
while the vif_table has only MAXVIFS (32) entries. The same problem
affects ip6mr.
Refuse invalid values to fix a potential out-of-bounds access. Unlike
the other validity checks, this is checked in ipmr_mfc_add() instead of
the setsockopt handler since its unused in the delete path and might be
uninitialized.
Signed-off-by: Patrick McHardy <kaber@trash.net>
---
net/ipv4/ipmr.c | 3 +++
net/ipv6/ip6mr.c | 3 +++
2 files changed, 6 insertions(+), 0 deletions(-)
diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index 8582e12..0b9d03c 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -802,6 +802,9 @@ static int ipmr_mfc_add(struct net *net, struct mfcctl *mfc, int mrtsock)
int line;
struct mfc_cache *uc, *c, **cp;
+ if (mfc->mfcc_parent >= MAXVIFS)
+ return -ENFILE;
+
line = MFC_HASH(mfc->mfcc_mcastgrp.s_addr, mfc->mfcc_origin.s_addr);
for (cp = &net->ipv4.mfc_cache_array[line];
diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c
index 52e0f74..23e4ac0 100644
--- a/net/ipv6/ip6mr.c
+++ b/net/ipv6/ip6mr.c
@@ -1113,6 +1113,9 @@ static int ip6mr_mfc_add(struct net *net, struct mf6cctl *mfc, int mrtsock)
unsigned char ttls[MAXMIFS];
int i;
+ if (mfc->mf6cc_parent >= MAXMIFS)
+ return -ENFILE;
+
memset(ttls, 255, MAXMIFS);
for (i = 0; i < MAXMIFS; i++) {
if (IF_ISSET(i, &mfc->mf6cc_ifset))
--
1.6.5.7
^ permalink raw reply related
* [C/R v20][PATCH 74/96] Add common socket helpers to unify the security hooks
From: Oren Laadan @ 2010-03-17 16:09 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-kernel, linux-mm, linux-api, Serge Hallyn, Ingo Molnar,
containers, Dan Smith, netdev
In-Reply-To: <1268842164-5590-74-git-send-email-orenl@cs.columbia.edu>
From: Dan Smith <danms@us.ibm.com>
This moves the meat out of the bind(), getsockname(), and getpeername() syscalls
into helper functions that performs security_socket_bind() and then the
sock->ops->call(). This allows a unification of this behavior between the
syscalls and the pending socket restart logic.
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: Serge E. Hallyn <serue@us.ibm.com>
Tested-by: Serge E. Hallyn <serue@us.ibm.com>
Cc: netdev@vger.kernel.org
---
include/net/sock.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
net/socket.c | 29 ++++++-----------------------
2 files changed, 54 insertions(+), 23 deletions(-)
diff --git a/include/net/sock.h b/include/net/sock.h
index 3f1a480..623eb19 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -1616,6 +1616,54 @@ extern void sock_enable_timestamp(struct sock *sk, int flag);
extern int sock_get_timestamp(struct sock *, struct timeval __user *);
extern int sock_get_timestampns(struct sock *, struct timespec __user *);
+/* bind() helper shared between any callers needing to perform a bind on
+ * behalf of userspace (syscall and restart) with the security hooks.
+ */
+static inline int sock_bind(struct socket *sock,
+ struct sockaddr *addr,
+ int addr_len)
+{
+ int err;
+
+ err = security_socket_bind(sock, addr, addr_len);
+ if (err)
+ return err;
+ else
+ return sock->ops->bind(sock, addr, addr_len);
+}
+
+/* getname() helper shared between any callers needing to perform a getname on
+ * behalf of userspace (syscall and restart) with the security hooks.
+ */
+static inline int sock_getname(struct socket *sock,
+ struct sockaddr *addr,
+ int *addr_len)
+{
+ int err;
+
+ err = security_socket_getsockname(sock);
+ if (err)
+ return err;
+ else
+ return sock->ops->getname(sock, addr, addr_len, 0);
+}
+
+/* getpeer() helper shared between any callers needing to perform a getpeer on
+ * behalf of userspace (syscall and restart) with the security hooks.
+ */
+static inline int sock_getpeer(struct socket *sock,
+ struct sockaddr *addr,
+ int *addr_len)
+{
+ int err;
+
+ err = security_socket_getpeername(sock);
+ if (err)
+ return err;
+ else
+ return sock->ops->getname(sock, addr, addr_len, 1);
+}
+
/*
* Enable debug/info messages
*/
diff --git a/net/socket.c b/net/socket.c
index 769c386..4d4fdc2 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -1421,15 +1421,10 @@ SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);
- if (err >= 0) {
- err = security_socket_bind(sock,
- (struct sockaddr *)&address,
- addrlen);
- if (!err)
- err = sock->ops->bind(sock,
- (struct sockaddr *)
- &address, addrlen);
- }
+ if (err >= 0)
+ err = sock_bind(sock,
+ (struct sockaddr *)&address,
+ addrlen);
fput_light(sock->file, fput_needed);
}
return err;
@@ -1608,11 +1603,7 @@ SYSCALL_DEFINE3(getsockname, int, fd, struct sockaddr __user *, usockaddr,
if (!sock)
goto out;
- err = security_socket_getsockname(sock);
- if (err)
- goto out_put;
-
- err = sock->ops->getname(sock, (struct sockaddr *)&address, &len, 0);
+ err = sock_getname(sock, (struct sockaddr *)&address, &len);
if (err)
goto out_put;
err = move_addr_to_user((struct sockaddr *)&address, len, usockaddr, usockaddr_len);
@@ -1637,15 +1628,7 @@ SYSCALL_DEFINE3(getpeername, int, fd, struct sockaddr __user *, usockaddr,
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock != NULL) {
- err = security_socket_getpeername(sock);
- if (err) {
- fput_light(sock->file, fput_needed);
- return err;
- }
-
- err =
- sock->ops->getname(sock, (struct sockaddr *)&address, &len,
- 1);
+ err = sock_getpeer(sock, (struct sockaddr *)&address, &len);
if (!err)
err = move_addr_to_user((struct sockaddr *)&address, len, usockaddr,
usockaddr_len);
--
1.6.3.3
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply related
* [C/R v20][PATCH 76/96] c/r: Add AF_UNIX support (v12)
From: Oren Laadan @ 2010-03-17 16:09 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-kernel, linux-mm, linux-api, Serge Hallyn, Ingo Molnar,
containers, Dan Smith, Alexey Dobriyan, netdev, Oren Laadan
In-Reply-To: <1268842164-5590-76-git-send-email-orenl@cs.columbia.edu>
From: Dan Smith <danms@us.ibm.com>
This patch adds basic checkpoint/restart support for AF_UNIX sockets. It
has been tested with a single and multiple processes, and with data inflight
at the time of checkpoint. It supports socketpair()s, path-based, and
abstract sockets.
Changes in ckpt-v19:
- [Serge Hallyn] skb->tail can be offset
Changes in ckpt-v19-rc3:
- Rebase to kernel 2.6.33: export and leverage sock_alloc_file()
- [Nathan Lynch] Fix net/checkpoint.c for 64-bit
Changes in ckpt-v19-rc2:
- Change select uses of ckpt_debug() to ckpt_err() in net c/r
- [Dan Smith] Unify skb read/write functions and handle fragmented buffers
- [Dan Smith] Update buffer restore code to match the new format
Changes in ckpt-v19-rc1:
- [Dan Smith] Fix compile issue with CONFIG_CHECKPOINT=n
- [Dan Smith] Remove an unnecessary check on socket restart
- [Matt Helsley] Add cpp definitions for enums
- [Dan Smith] Pass the stored sock->protocol into sock_create() on restore
Changes in v12:
- Collect sockets for leak-detection
- Adjust socket reference count during leak detection phase
Changes in v11:
- Create a struct socket for orphan socket during checkpoint
- Make sockets proper objhash objects and use checkpoint_obj() on them
- Rename headerless struct ckpt_hdr_* to struct ckpt_*
- Remove struct timeval from socket header
- Save and restore UNIX socket peer credentials
- Set socket flags on restore using sock_setsockopt() where possible
- Fail on the TIMESTAMPING_* flags for the moment (with a TODO)
- Remove other explicit flag checks that are no longer copied blindly
- Changed functions/variables names to follow existing conventions
- Use proto_ops->{checkpoint,restart} methods for af_unix
- Cleanup sock_file_restore()/sock_file_checkpoint()
- Make ckpt_hdr_socket be part of ckpt_hdr_file_socket
- Fold do_sock_file_checkpoint() into sock_file_checkpoint()
- Fold do_sock_file_restore() into sock_file_restore()
- Move sock_file_{checkpoint,restore} to net/checkpoint.c
- Properly define sock_file_{checkpoint,restore} in header file
- sock_file_restore() now calls restore_file_common()
Changes in v10:
- Moved header structure definitions back to checkpoint_hdr.h
- Moved AF_UNIX checkpoint/restart code to net/unix/checkpoint.c
- Make sock_unix_*() functions only compile if CONFIG_UNIX=y
- Add TODO for CONFIG_UNIX=m case
Changes in v9:
- Fix double-free of skb's in the list and target holding queue in the
error path of sock_copy_buffers()
- Adjust use of ckpt_read_string() to match new signature
Changes in v8:
- Fix stale dev_alloc_skb() from before the conversion to skb_clone()
- Fix a couple of broken error paths
- Fix memory leak of kvec.iov_base on successful return from sendmsg()
- Fix condition for deciding when to run sock_cptrst_verify()
- Fix buffer queue copy algorithm to hold the lock during walk(s)
- Log the errno when either getname() or getpeer() fails
- Add comments about ancillary messages in the UNIX queue
- Add TODO comments for credential restore and flags via setsockopt()
- Add TODO comment about strangely-connected dgram sockets and the use
of sendmsg(peer)
Changes in v7:
- Fix failure to free iov_base in error path of sock_read_buffer()
- Change sock_read_buffer() to use _ckpt_read_obj_type() to get the
header length and then use ckpt_kread() directly to read the payload
- Change sock_read_buffers() to sock_unix_read_buffers() and break out
some common functionality to better accommodate the subsequent INET
patch
- Generalize sock_unix_getnames() into sock_getnames() so INET can use it
- Change skb_morph() to skb_clone() which uses the more common path and
still avoids the copy
- Add check to validate the socket type before creating socket
on restore
- Comment the CAP_NET_ADMIN override in sock_read_buffer_hdr
- Strengthen the comment about priming the buffer limits
- Change the objhash functions to deny direct checkpoint of sockets and
remove the reference counting function
- Change SOCKET_BUFFERS to SOCKET_QUEUE
- Change this,peer objrefs to signed integers
- Remove names from internal socket structures
- Fix handling of sock_copy_buffers() result
- Use ckpt_fill_fname() instead of d_path() for writing CWD
- Use sock_getname() and sock_getpeer() for proper security hookage
- Return -ENOSYS for unsupported socket families in checkpoint and restart
- Use sock_setsockopt() and sock_getsockopt() where possible to save and
restore socket option values
- Check for SOCK_DESTROY flag in the global verify function because none
of our supported socket types use it
- Check for SOCK_USE_WRITE_QUEUE in AF_UNIX restore function because
that flag should not be used on such a socket
- Check socket state in UNIX restart path to validate the subset of valid
values
Changes in v6:
- Moved the socket addresses to the per-type header
- Eliminated the HASCWD flag
- Remove use of ckpt_write_err() in restart paths
- Change the order in which buffers are read so that we can set the
socket's limit equal to the size of the image's buffers (if appropriate)
and then restore the original values afterwards.
- Use the ckpt_validate_errno() helper
- Add a check to make sure that we didn't restore a (UNIX) socket with
any skb's in the send buffer
- Fix up sock_unix_join() to not leave addr uninitialized for socketpair
- Remove inclusion of checkpoint_hdr.h in the socket files
- Make sock_unix_write_cwd() use ckpt_write_string() and use the new
ckpt_read_string() for reading the cwd
- Use the restored realcred credentials in sock_unix_join()
- Fix error path of the chdir_and_bind
- Change the algorithm for reloading the socket buffers to use sendmsg()
on the socket's peer for better accounting
- For DGRAM sockets, check the backlog value against the system max
to avoid letting a restart bypass the overloaded queue length
- Use sock_bind() instead of sock->ops->bind() to gain the security hook
- Change "restart" to "restore" in some of the function names
Changes in v5:
- Change laddr and raddr buffers in socket header to be long enough
for INET6 addresses
- Place socket.c and sock.h function definitions inside #ifdef
CONFIG_CHECKPOINT
- Add explicit check in sock_unix_makeaddr() to refuse if the
checkpoint image specifies an addr length of 0
- Split sock_unix_restart() into a few pieces to facilitate:
- Changed behavior of the unix restore code so that unlinked LISTEN
sockets don't do a bind()...unlink()
- Save the base path of a bound socket's path so that we can chdir()
to the base before bind() if it is a relative path
- Call bind() for any socket that is not established but has a
non-zero-length local address
- Enforce the current sysctl limit on socket buffer size during restart
unless the user holds CAP_NET_ADMIN
- Unlink a path-based socket before calling bind()
Changes in v4:
- Changed the signdness of rcvlowat, rcvtimeo, sndtimeo, and backlog
to match their struct sock definitions. This should avoid issues
with sign extension.
- Add a sock_cptrst_verify() function to be run at restore time to
validate several of the values in the checkpoint image against
limits, flag masks, etc.
- Write an error string with ctk_write_err() in the obscure cases
- Don't write socket buffers for listen sockets
- Sanity check address lengths before we agree to allocate memory
- Check the result of inserting the peer object in the objhash on
restart
- Check return value of sock_cptrst() on restart
- Change logic in remote getname() phase of checkpoint to not fail for
closed (et al) sockets
- Eliminate the memory copy while reading socket buffers on restart
Changes in v3:
- Move sock_file_checkpoint() above sock_file_restore()
- Change __sock_file_*() functions to do_sock_file_*()
- Adjust some of the struct cr_hdr_socket alignment
- Improve the sock_copy_buffers() algorithm to avoid locking the source
queue for the entire operation
- Fix alignment in the socket header struct(s)
- Move the per-protocol structure (ckpt_hdr_socket_un) out of the
common socket header and read/write it separately
- Fix missing call to sock_cptrst() in restore path
- Break out the socket joining into another function
- Fix failure to restore the socket address thus fixing getname()
- Check the state values on restart
- Fix case of state being TCP_CLOSE, which allows dgram sockets to be
properly connected (if appropriate) to their peer and maintain the
sockaddr for getname() operation
- Fix restoring a listening socket that has been unlink()'d
- Fix checkpointing sockets with an in-flight FD-passing SKB. Fail
with EBUSY.
- Fix checkpointing listening sockets with an unaccepted connection.
Fail with EBUSY.
- Changed 'un' to 'unix' in function and structure names
Changes in v2:
- Change GFP_KERNEL to GFP_ATOMIC in sock_copy_buffers() (this seems
to be rather common in other uses of skb_copy())
- Move the ckpt_hdr_socket structure definition to linux/socket.h
- Fix whitespace issue
- Move sock_file_checkpoint() to net/socket.c for symmetry
Cc: Alexey Dobriyan <adobriyan@gmail.com>
Cc: netdev@vger.kernel.org
Acked-by: Serge Hallyn <serue@us.ibm.com>
Acked-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Dan Smith <danms@us.ibm.com>
Signed-off-by: Oren Laadan <orenl@cs.columbia.edu>
---
checkpoint/files.c | 7 +
checkpoint/objhash.c | 69 +++
include/linux/checkpoint.h | 8 +
include/linux/checkpoint_hdr.h | 117 +++++-
include/linux/net.h | 2 +
include/net/af_unix.h | 15 +
include/net/sock.h | 12 +
net/Makefile | 2 +
net/checkpoint.c | 983 ++++++++++++++++++++++++++++++++++++++++
net/socket.c | 6 +-
net/unix/Makefile | 1 +
net/unix/af_unix.c | 9 +
net/unix/checkpoint.c | 647 ++++++++++++++++++++++++++
13 files changed, 1872 insertions(+), 6 deletions(-)
create mode 100644 net/checkpoint.c
create mode 100644 net/unix/checkpoint.c
diff --git a/checkpoint/files.c b/checkpoint/files.c
index 63a611f..900d1c1 100644
--- a/checkpoint/files.c
+++ b/checkpoint/files.c
@@ -22,6 +22,7 @@
#include <linux/deferqueue.h>
#include <linux/checkpoint.h>
#include <linux/checkpoint_hdr.h>
+#include <net/sock.h>
/**************************************************************************
@@ -624,6 +625,12 @@ static struct restore_file_ops restore_file_ops[] = {
.file_type = CKPT_FILE_FIFO,
.restore = fifo_file_restore,
},
+ /* socket */
+ {
+ .file_name = "SOCKET",
+ .file_type = CKPT_FILE_SOCKET,
+ .restore = sock_file_restore,
+ },
};
static struct file *do_restore_file(struct ckpt_ctx *ctx)
diff --git a/checkpoint/objhash.c b/checkpoint/objhash.c
index ea2f063..b1f257f 100644
--- a/checkpoint/objhash.c
+++ b/checkpoint/objhash.c
@@ -20,6 +20,7 @@
#include <linux/user_namespace.h>
#include <linux/checkpoint.h>
#include <linux/checkpoint_hdr.h>
+#include <net/sock.h>
struct ckpt_obj;
struct ckpt_obj_ops;
@@ -234,6 +235,40 @@ static void obj_groupinfo_drop(void *ptr, int lastref)
put_group_info((struct group_info *) ptr);
}
+static int obj_sock_grab(void *ptr)
+{
+ sock_hold((struct sock *) ptr);
+ return 0;
+}
+
+static void obj_sock_drop(void *ptr, int lastref)
+{
+ struct sock *sk = (struct sock *) ptr;
+
+ /*
+ * Sockets created during restart are graft()ed, i.e. have a
+ * valid @sk->sk_socket. Because only an fput() results in the
+ * necessary sock_release(), we may leak the struct socket of
+ * sockets that were not attached to a file. Therefore, if
+ * @lastref is set, we hereby invoke sock_release() on sockets
+ * that we have put into the objhash but were never attached
+ * to a file.
+ */
+ if (lastref && sk->sk_socket && !sk->sk_socket->file) {
+ struct socket *sock = sk->sk_socket;
+ sock_orphan(sk);
+ sock->sk = NULL;
+ sock_release(sock);
+ }
+
+ sock_put((struct sock *) ptr);
+}
+
+static int obj_sock_users(void *ptr)
+{
+ return atomic_read(&((struct sock *) ptr)->sk_refcnt);
+}
+
static struct ckpt_obj_ops ckpt_obj_ops[] = {
/* ignored object */
{
@@ -362,6 +397,16 @@ static struct ckpt_obj_ops ckpt_obj_ops[] = {
.checkpoint = checkpoint_groupinfo,
.restore = restore_groupinfo,
},
+ /* sock object */
+ {
+ .obj_name = "SOCKET",
+ .obj_type = CKPT_OBJ_SOCK,
+ .ref_drop = obj_sock_drop,
+ .ref_grab = obj_sock_grab,
+ .ref_users = obj_sock_users,
+ .checkpoint = checkpoint_sock,
+ .restore = restore_sock,
+ },
};
@@ -756,6 +801,26 @@ static void ckpt_obj_users_inc(struct ckpt_ctx *ctx, void *ptr, int increment)
*/
/**
+ * obj_sock_adjust_users - remove implicit reference on DEAD sockets
+ * @obj: CKPT_OBJ_SOCK object to adjust
+ *
+ * Sockets that have been disconnected from their struct file have
+ * a reference count one less than normal sockets. The objhash's
+ * assumption of such a reference is therefore incorrect, so we correct
+ * it here.
+ */
+static inline void obj_sock_adjust_users(struct ckpt_obj *obj)
+{
+ struct sock *sk = (struct sock *)obj->ptr;
+
+ if (sock_flag(sk, SOCK_DEAD)) {
+ obj->users--;
+ ckpt_debug("Adjusting SOCK %i count to %i\n",
+ obj->objref, obj->users);
+ }
+}
+
+/**
* ckpt_obj_contained - test if shared objects are contained in checkpoint
* @ctx: checkpoint context
*
@@ -780,6 +845,10 @@ int ckpt_obj_contained(struct ckpt_ctx *ctx)
hlist_for_each_entry(obj, node, &ctx->obj_hash->list, next) {
if (!obj->ops->ref_users)
continue;
+
+ if (obj->ops->obj_type == CKPT_OBJ_SOCK)
+ obj_sock_adjust_users(obj);
+
if (obj->ops->ref_users(obj->ptr) != obj->users) {
ckpt_err(ctx, -EBUSY,
"%(O)%(P)%(S)Usage leak (%d != %d)\n",
diff --git a/include/linux/checkpoint.h b/include/linux/checkpoint.h
index 220388d..e0f4bd1 100644
--- a/include/linux/checkpoint.h
+++ b/include/linux/checkpoint.h
@@ -33,6 +33,7 @@
#include <linux/checkpoint_types.h>
#include <linux/checkpoint_hdr.h>
#include <linux/err.h>
+#include <net/sock.h>
/* sycall helpers */
extern long do_sys_checkpoint(pid_t pid, int fd,
@@ -97,6 +98,13 @@ extern int restore_read_page(struct ckpt_ctx *ctx, struct page *page);
/* pids */
extern pid_t ckpt_pid_nr(struct ckpt_ctx *ctx, struct pid *pid);
+/* socket functions */
+extern int ckpt_sock_getnames(struct ckpt_ctx *ctx,
+ struct socket *socket,
+ struct sockaddr *loc, unsigned *loc_len,
+ struct sockaddr *rem, unsigned *rem_len);
+extern struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx);
+
/* ckpt kflags */
#define ckpt_set_ctx_kflag(__ctx, __kflag) \
set_bit(__kflag##_BIT, &(__ctx)->kflags)
diff --git a/include/linux/checkpoint_hdr.h b/include/linux/checkpoint_hdr.h
index f0a41ec..ad2f0f2 100644
--- a/include/linux/checkpoint_hdr.h
+++ b/include/linux/checkpoint_hdr.h
@@ -10,13 +10,15 @@
* distribution for more details.
*/
-#ifndef __KERNEL__
-#include <sys/types.h>
-#include <linux/types.h>
-#endif
-
#ifdef __KERNEL__
#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/un.h>
+#else
+#include <sys/types.h>
+#include <linux/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
#endif
/*
@@ -140,6 +142,17 @@ enum {
CKPT_HDR_SIGPENDING,
#define CKPT_HDR_SIGPENDING CKPT_HDR_SIGPENDING
+ CKPT_HDR_SOCKET = 701,
+#define CKPT_HDR_SOCKET CKPT_HDR_SOCKET
+ CKPT_HDR_SOCKET_QUEUE,
+#define CKPT_HDR_SOCKET_QUEUE CKPT_HDR_SOCKET_QUEUE
+ CKPT_HDR_SOCKET_BUFFER,
+#define CKPT_HDR_SOCKET_BUFFER CKPT_HDR_SOCKET_BUFFER
+ CKPT_HDR_SOCKET_FRAG,
+#define CKPT_HDR_SOCKET_FRAG CKPT_HDR_SOCKET_FRAG
+ CKPT_HDR_SOCKET_UNIX,
+#define CKPT_HDR_SOCKET_UNIX CKPT_HDR_SOCKET_UNIX
+
CKPT_HDR_TAIL = 9001,
#define CKPT_HDR_TAIL CKPT_HDR_TAIL
@@ -195,6 +208,8 @@ enum obj_type {
#define CKPT_OBJ_USER CKPT_OBJ_USER
CKPT_OBJ_GROUPINFO,
#define CKPT_OBJ_GROUPINFO CKPT_OBJ_GROUPINFO
+ CKPT_OBJ_SOCK,
+#define CKPT_OBJ_SOCK CKPT_OBJ_SOCK
CKPT_OBJ_MAX
#define CKPT_OBJ_MAX CKPT_OBJ_MAX
};
@@ -444,6 +459,8 @@ enum file_type {
#define CKPT_FILE_PIPE CKPT_FILE_PIPE
CKPT_FILE_FIFO,
#define CKPT_FILE_FIFO CKPT_FILE_FIFO
+ CKPT_FILE_SOCKET,
+#define CKPT_FILE_SOCKET CKPT_FILE_SOCKET
CKPT_FILE_MAX
#define CKPT_FILE_MAX CKPT_FILE_MAX
};
@@ -468,6 +485,96 @@ struct ckpt_hdr_file_pipe {
__s32 pipe_objref;
} __attribute__((aligned(8)));
+/* socket */
+struct ckpt_hdr_socket {
+ struct ckpt_hdr h;
+
+ struct { /* struct socket */
+ __u64 flags;
+ __u8 state;
+ } socket __attribute__ ((aligned(8)));
+
+ struct { /* struct sock_common */
+ __u32 bound_dev_if;
+ __u32 reuse;
+ __u16 family;
+ __u8 state;
+ } sock_common __attribute__ ((aligned(8)));
+
+ struct { /* struct sock */
+ __s64 rcvlowat;
+ __u64 flags;
+
+ __s64 rcvtimeo;
+ __s64 sndtimeo;
+
+ __u32 err;
+ __u32 err_soft;
+ __u32 priority;
+ __s32 rcvbuf;
+ __s32 sndbuf;
+ __u16 type;
+ __s16 backlog;
+
+ __u8 protocol;
+ __u8 state;
+ __u8 shutdown;
+ __u8 userlocks;
+ __u8 no_check;
+
+ struct linger linger;
+ } sock __attribute__ ((aligned(8)));
+} __attribute__ ((aligned(8)));
+
+struct ckpt_hdr_socket_queue {
+ struct ckpt_hdr h;
+ __u32 skb_count;
+ __u32 total_bytes;
+} __attribute__ ((aligned(8)));
+
+struct ckpt_hdr_socket_buffer {
+ struct ckpt_hdr h;
+ __u32 transport_header;
+ __u32 network_header;
+ __u32 mac_header;
+ __u32 lin_len; /* Length of linear data */
+ __u32 frg_len; /* Length of fragment data */
+ __u32 skb_len; /* Length of skb (adjusted) */
+ __u32 hdr_len; /* Length of skipped header */
+ __u32 mac_len;
+ __u32 data_offset; /* Offset of data pointer from head */
+ __s32 sk_objref;
+ __s32 pr_objref;
+ __u16 protocol;
+ __u16 nr_frags;
+ __u8 cb[48];
+};
+
+struct ckpt_hdr_socket_buffer_frag {
+ struct ckpt_hdr h;
+ __u32 size;
+ __u32 offset;
+};
+
+#define CKPT_UNIX_LINKED 1
+struct ckpt_hdr_socket_unix {
+ struct ckpt_hdr h;
+ __s32 this;
+ __s32 peer;
+ __u32 peercred_uid;
+ __u32 peercred_gid;
+ __u32 flags;
+ __u32 laddr_len;
+ __u32 raddr_len;
+ struct sockaddr_un laddr;
+ struct sockaddr_un raddr;
+} __attribute__ ((aligned(8)));
+
+struct ckpt_hdr_file_socket {
+ struct ckpt_hdr_file common;
+ __s32 sock_objref;
+} __attribute__((aligned(8)));
+
/* memory layout */
struct ckpt_hdr_mm {
struct ckpt_hdr h;
diff --git a/include/linux/net.h b/include/linux/net.h
index 72a53b9..9548e45 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -242,6 +242,8 @@ extern int sock_sendmsg(struct socket *sock, struct msghdr *msg,
size_t len);
extern int sock_recvmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags);
+extern int sock_alloc_file(struct socket *sock, struct file **f,
+ int flags);
extern int sock_map_fd(struct socket *sock, int flags);
extern struct socket *sockfd_lookup(int fd, int *err);
#define sockfd_put(sock) fput(sock->file)
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 1614d78..ee423d1 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -68,4 +68,19 @@ static inline int unix_sysctl_register(struct net *net) { return 0; }
static inline void unix_sysctl_unregister(struct net *net) {}
#endif
#endif
+
+#ifdef CONFIG_CHECKPOINT
+struct ckpt_ctx;
+struct ckpt_hdr_socket;
+extern int unix_checkpoint(struct ckpt_ctx *ctx, struct socket *sock);
+extern int unix_restore(struct ckpt_ctx *ctx, struct socket *sock,
+ struct ckpt_hdr_socket *h);
+extern int unix_collect(struct ckpt_ctx *ctx, struct socket *sock);
+
+#else
+#define unix_checkpoint NULL
+#define unix_restore NULL
+#define unix_collect NULL
+#endif /* CONFIG_CHECKPOINT */
+
#endif
diff --git a/include/net/sock.h b/include/net/sock.h
index 623eb19..6d5f5b3 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -1684,4 +1684,16 @@ extern int sysctl_optmem_max;
extern __u32 sysctl_wmem_default;
extern __u32 sysctl_rmem_default;
+#ifdef CONFIG_CHECKPOINT
+/* Checkpoint/Restart Functions */
+struct ckpt_ctx;
+struct ckpt_hdr_file;
+extern int checkpoint_sock(struct ckpt_ctx *ctx, void *ptr);
+extern void *restore_sock(struct ckpt_ctx *ctx);
+extern int sock_file_checkpoint(struct ckpt_ctx *ctx, struct file *file);
+extern struct file *sock_file_restore(struct ckpt_ctx *ctx,
+ struct ckpt_hdr_file *h);
+extern int sock_file_collect(struct ckpt_ctx *ctx, struct file *file);
+#endif
+
#endif /* _SOCK_H */
diff --git a/net/Makefile b/net/Makefile
index 1542e72..74b038f 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -65,3 +65,5 @@ ifeq ($(CONFIG_NET),y)
obj-$(CONFIG_SYSCTL) += sysctl_net.o
endif
obj-$(CONFIG_WIMAX) += wimax/
+
+obj-$(CONFIG_CHECKPOINT) += checkpoint.o
diff --git a/net/checkpoint.c b/net/checkpoint.c
new file mode 100644
index 0000000..386a0c6
--- /dev/null
+++ b/net/checkpoint.c
@@ -0,0 +1,983 @@
+/*
+ * Copyright 2009 IBM Corporation
+ *
+ * Author(s): Dan Smith <danms@us.ibm.com>
+ * Oren Laadan <orenl@cs.columbia.edu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/socket.h>
+#include <linux/mount.h>
+#include <linux/file.h>
+#include <linux/namei.h>
+#include <linux/syscalls.h>
+#include <linux/sched.h>
+#include <linux/fs_struct.h>
+#include <linux/highmem.h>
+
+#include <net/af_unix.h>
+#include <net/tcp_states.h>
+#include <net/tcp.h>
+
+#include <linux/deferqueue.h>
+#include <linux/checkpoint.h>
+#include <linux/checkpoint_hdr.h>
+
+struct dq_buffers {
+ struct ckpt_ctx *ctx;
+ struct sock *sk;
+};
+
+static int sock_copy_buffers(struct sk_buff_head *from,
+ struct sk_buff_head *to,
+ uint32_t *total_bytes)
+{
+ int count1 = 0;
+ int count2 = 0;
+ int i;
+ struct sk_buff *skb;
+ struct sk_buff **skbs;
+
+ *total_bytes = 0;
+
+ spin_lock(&from->lock);
+ skb_queue_walk(from, skb)
+ count1++;
+ spin_unlock(&from->lock);
+
+ skbs = kzalloc(sizeof(*skbs) * count1, GFP_KERNEL);
+ if (!skbs)
+ return -ENOMEM;
+
+ for (i = 0; i < count1; i++) {
+ skbs[i] = dev_alloc_skb(0);
+ if (!skbs[i])
+ goto err;
+ }
+
+ i = 0;
+ spin_lock(&from->lock);
+ skb_queue_walk(from, skb) {
+ if (++count2 > count1)
+ break; /* The queue changed as we read it */
+
+ skb_morph(skbs[i], skb);
+ skbs[i]->sk = skb->sk;
+ skb_queue_tail(to, skbs[i]);
+
+ *total_bytes += skb->len;
+ i++;
+ }
+ spin_unlock(&from->lock);
+
+ if (count1 != count2)
+ goto err;
+
+ kfree(skbs);
+
+ return count1;
+ err:
+ while (skb_dequeue(to))
+ ; /* Pull all the buffers out of the queue */
+ for (i = 0; i < count1; i++)
+ kfree_skb(skbs[i]);
+ kfree(skbs);
+
+ return -EAGAIN;
+}
+
+static void sock_record_header_info(struct sk_buff *skb,
+ struct ckpt_hdr_socket_buffer *h)
+{
+
+ h->mac_len = skb->mac_len;
+ h->skb_len = skb->len;
+ h->hdr_len = skb->data - skb->head;
+ h->frg_len = skb->data_len;
+ h->data_offset = (skb->data - skb->head);
+
+#ifdef NET_SKBUFF_DATA_USES_OFFSET
+ h->transport_header = skb->transport_header;
+ h->network_header = skb->network_header;
+ h->mac_header = skb->mac_header;
+ h->lin_len = (unsigned long) skb->tail;
+#else
+ h->transport_header = skb->transport_header - skb->head;
+ h->network_header = skb->network_header - skb->head;
+ h->mac_header = skb->mac_header - skb->head;
+ h->lin_len = ((unsigned long) skb->tail - (unsigned long) skb->head);
+#endif
+
+ memcpy(h->cb, skb->cb, sizeof(skb->cb));
+ h->nr_frags = skb_shinfo(skb)->nr_frags;
+}
+
+int sock_restore_header_info(struct ckpt_ctx *ctx,
+ struct sk_buff *skb,
+ struct ckpt_hdr_socket_buffer *h)
+{
+ if (h->mac_header + h->mac_len != h->network_header) {
+ ckpt_err(ctx, -EINVAL,
+ "skb mac_header %u+%u != network header %u\n",
+ h->mac_header, h->mac_len, h->network_header);
+ return -EINVAL;
+ }
+
+ if (h->network_header > h->lin_len) {
+ ckpt_err(ctx, -EINVAL,
+ "skb network header %u > linear length %u\n",
+ h->network_header, h->lin_len);
+ return -EINVAL;
+ }
+
+ if (h->transport_header > h->lin_len) {
+ ckpt_err(ctx, -EINVAL,
+ "skb transport header %u > linear length %u\n",
+ h->transport_header, h->lin_len);
+ return -EINVAL;
+ }
+
+ if (h->data_offset > h->lin_len) {
+ ckpt_err(ctx, -EINVAL,
+ "skb data offset %u > linear length %u\n",
+ h->data_offset, h->lin_len);
+ return -EINVAL;
+ }
+
+ if (h->skb_len > SKB_MAX_ALLOC) {
+ ckpt_err(ctx, -EINVAL,
+ "skb total length %u larger than max of %lu\n",
+ h->skb_len, SKB_MAX_ALLOC);
+ return -EINVAL;
+ }
+
+ skb_set_transport_header(skb, h->transport_header);
+ skb_set_network_header(skb, h->network_header);
+ skb_set_mac_header(skb, h->mac_header);
+ skb->mac_len = h->mac_len;
+
+ /* FIXME: This should probably be sanitized per-protocol to
+ * make sure nothing bad happens if it is hijacked. For the
+ * current set of protocols that we restore this way, the data
+ * contained within is not very risky (flags and sequence
+ * numbers) but could still be evalutated from a
+ * could-the-user- have-set-these-flags point of view.
+ */
+ memcpy(skb->cb, h->cb, sizeof(skb->cb));
+
+ skb->data = skb->head + h->data_offset;
+ skb->len = h->skb_len;
+
+ return 0;
+}
+
+static int sock_restore_skb_frag(struct ckpt_ctx *ctx,
+ struct sk_buff *skb,
+ int frag_idx)
+{
+ struct ckpt_hdr_socket_buffer_frag *h;
+ struct page *page;
+ int ret = 0;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_FRAG);
+ if (IS_ERR(h)) {
+ ckpt_err(ctx, PTR_ERR(h), "failed to read buffer object\n");
+ return PTR_ERR(h);
+ }
+
+ if ((h->size > PAGE_SIZE) || (h->offset >= PAGE_SIZE)) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "skb frag size=%i,offset=%i > PAGE_SIZE\n",
+ h->size, h->offset);
+ goto out;
+ }
+
+ page = alloc_page(GFP_KERNEL);
+ if (!page) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = restore_read_page(ctx, page);
+ if (ret) {
+ ckpt_err(ctx, ret, "failed to read fragment: %i\n", ret);
+ __free_page(page);
+ } else {
+ ckpt_debug("read %i+%i for fragment %i\n",
+ h->offset, h->size, frag_idx);
+ skb_add_rx_frag(skb, frag_idx, page, h->offset, h->size);
+ ret = h->size;
+ }
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_socket_buffer *h;
+ struct sk_buff *skb = NULL;
+ int i, ret;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_BUFFER);
+ if (IS_ERR(h))
+ return (struct sk_buff *)h;
+
+ ret = -ENOSPC;
+ if (h->lin_len > SKB_MAX_ALLOC) {
+ ckpt_err(ctx, ret, "socket linear buffer too big (%u > %lu)\n",
+ h->lin_len, SKB_MAX_ALLOC);
+ goto out;
+ } else if (h->frg_len > SKB_MAX_ALLOC) {
+ ckpt_err(ctx, ret, "socket frag size too big (%u > %lu\n",
+ h->frg_len, SKB_MAX_ALLOC);
+ goto out;
+ } else if (h->nr_frags >= MAX_SKB_FRAGS) {
+ ckpt_err(ctx, ret, "socket frag count too big (%u > %lu\n",
+ h->nr_frags, MAX_SKB_FRAGS);
+ goto out;
+ }
+
+ skb = alloc_skb(h->lin_len, GFP_KERNEL);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = _ckpt_read_obj_type(ctx, skb_put(skb, h->lin_len),
+ h->lin_len, CKPT_HDR_BUFFER);
+ ckpt_debug("read linear skb length %u: %i\n", h->lin_len, ret);
+ if (ret < 0)
+ goto out;
+
+ for (i = 0; i < h->nr_frags; i++) {
+ ret = sock_restore_skb_frag(ctx, skb, i);
+ ckpt_debug("read skb frag %i/%i: %i\n",
+ i + 1, h->nr_frags, ret);
+ if (ret < 0)
+ goto out;
+ h->frg_len -= ret;
+ }
+
+ if (h->frg_len != 0) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "length %u remaining after reading frags\n",
+ h->frg_len);
+ goto out;
+ }
+
+ sock_restore_header_info(ctx, skb, h);
+ out:
+ ckpt_hdr_put(ctx, h);
+ if (ret < 0) {
+ kfree_skb(skb);
+ skb = ERR_PTR(ret);
+ }
+
+ return skb;
+}
+
+static int __sock_write_skb_frag(struct ckpt_ctx *ctx, skb_frag_t *frag)
+{
+ struct ckpt_hdr_socket_buffer_frag *h;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_FRAG);
+ if (!h)
+ return -ENOMEM;
+
+ h->size = frag->size;
+ h->offset = frag->page_offset;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *)h);
+ ckpt_hdr_put(ctx, h);
+ if (ret < 0)
+ return ret;
+
+ ret = checkpoint_dump_page(ctx, frag->page);
+ ckpt_debug("writing frag page: %i\n", ret);
+ return ret;
+}
+
+static int __sock_write_skb(struct ckpt_ctx *ctx,
+ struct sk_buff *skb,
+ int dst_objref)
+{
+ struct ckpt_hdr_socket_buffer *h;
+ int ret = 0;
+ int i;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_BUFFER);
+ if (!h)
+ return -ENOMEM;
+
+ if (dst_objref > 0) {
+ BUG_ON(!skb->sk);
+ ret = checkpoint_obj(ctx, skb->sk, CKPT_OBJ_SOCK);
+ if (ret < 0)
+ goto out;
+ h->sk_objref = ret;
+ h->pr_objref = dst_objref;
+ }
+
+ sock_record_header_info(skb, h);
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_obj_type(ctx, skb->head, h->lin_len, CKPT_HDR_BUFFER);
+ ckpt_debug("writing skb linear region %u: %i\n", h->lin_len, ret);
+ if (ret < 0)
+ goto out;
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ ret = __sock_write_skb_frag(ctx, frag);
+ ckpt_debug("writing buffer fragment %i/%i (%i)\n",
+ i + 1, h->nr_frags, ret);
+ if (ret < 0)
+ goto out;
+ h->frg_len -= frag->size;
+ }
+
+ WARN_ON(h->frg_len != 0);
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+static int __sock_write_buffers(struct ckpt_ctx *ctx,
+ struct sk_buff_head *queue,
+ uint16_t family,
+ int dst_objref)
+{
+ struct sk_buff *skb;
+
+ skb_queue_walk(queue, skb) {
+ int ret = 0;
+
+ if (UNIXCB(skb).fp) {
+ ckpt_err(ctx, -EBUSY, "%(T)af_unix: pass fd\n");
+ return -EBUSY;
+ }
+
+ /* The other ancillary messages UNIX are always
+ * present unlike descriptors. Even though we can't
+ * detect them and fail the checkpoint, we're not at
+ * risk because we don't restore the control
+ * information in the UNIX code.
+ */
+
+ ret = __sock_write_skb(ctx, skb, dst_objref);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sock_write_buffers(struct ckpt_ctx *ctx,
+ struct sk_buff_head *queue,
+ uint16_t family,
+ int dst_objref)
+{
+ struct ckpt_hdr_socket_queue *h;
+ struct sk_buff_head tmpq;
+ int ret = -ENOMEM;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_QUEUE);
+ if (!h)
+ return -ENOMEM;
+
+ skb_queue_head_init(&tmpq);
+
+ ret = sock_copy_buffers(queue, &tmpq, &h->total_bytes);
+ if (ret < 0)
+ goto out;
+
+ h->skb_count = ret;
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ if (!ret)
+ ret = __sock_write_buffers(ctx, &tmpq, family, dst_objref);
+
+ out:
+ ckpt_hdr_put(ctx, h);
+ __skb_queue_purge(&tmpq);
+
+ return ret;
+}
+
+int sock_deferred_write_buffers(void *data)
+{
+ struct dq_buffers *dq = (struct dq_buffers *)data;
+ struct ckpt_ctx *ctx = dq->ctx;
+ int ret;
+ int dst_objref;
+
+ dst_objref = ckpt_obj_lookup(ctx, dq->sk, CKPT_OBJ_SOCK);
+ if (dst_objref < 0) {
+ ckpt_err(ctx, dst_objref, "%(T)socket: owner gone?\n");
+ return dst_objref;
+ }
+
+ ret = sock_write_buffers(ctx, &dq->sk->sk_receive_queue,
+ dq->sk->sk_family, dst_objref);
+ ckpt_debug("write recv buffers: %i\n", ret);
+ if (ret < 0)
+ return ret;
+
+ ret = sock_write_buffers(ctx, &dq->sk->sk_write_queue,
+ dq->sk->sk_family, dst_objref);
+ ckpt_debug("write send buffers: %i\n", ret);
+
+ return ret;
+}
+
+int sock_defer_write_buffers(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct dq_buffers dq;
+
+ dq.ctx = ctx;
+ dq.sk = sk;
+
+ /* NB: This is safe to do inside deferqueue_run() since it uses
+ * list_for_each_safe()
+ */
+ return deferqueue_add(ctx->files_deferq, &dq, sizeof(dq),
+ sock_deferred_write_buffers, NULL);
+}
+
+int ckpt_sock_getnames(struct ckpt_ctx *ctx, struct socket *sock,
+ struct sockaddr *loc, unsigned *loc_len,
+ struct sockaddr *rem, unsigned *rem_len)
+{
+ int ret;
+
+ ret = sock_getname(sock, loc, loc_len);
+ if (ret) {
+ ckpt_err(ctx, ret, "%(T)%(P)socket: getname local\n", sock);
+ return -EINVAL;
+ }
+
+ ret = sock_getpeer(sock, rem, rem_len);
+ if (ret) {
+ if ((sock->sk->sk_type != SOCK_DGRAM) &&
+ (sock->sk->sk_state == TCP_ESTABLISHED)) {
+ ckpt_err(ctx, ret, "%(T)%(P)socket: getname peer\n",
+ sock);
+ return -EINVAL;
+ }
+ *rem_len = 0;
+ }
+
+ return 0;
+}
+
+static int sock_cptrst_verify(struct ckpt_hdr_socket *h)
+{
+ uint8_t userlocks_mask =
+ SOCK_SNDBUF_LOCK | SOCK_RCVBUF_LOCK |
+ SOCK_BINDADDR_LOCK | SOCK_BINDPORT_LOCK;
+
+ if (h->sock.shutdown & ~SHUTDOWN_MASK)
+ return -EINVAL;
+ if (h->sock.userlocks & ~userlocks_mask)
+ return -EINVAL;
+ if (!ckpt_validate_errno(h->sock.err))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int sock_cptrst_opt(int op, struct socket *sock,
+ int optname, char *opt, int len)
+{
+ mm_segment_t fs;
+ int ret;
+
+ fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ if (op == CKPT_CPT)
+ ret = sock_getsockopt(sock, SOL_SOCKET, optname, opt, &len);
+ else
+ ret = sock_setsockopt(sock, SOL_SOCKET, optname, opt, len);
+
+ set_fs(fs);
+
+ return ret;
+}
+
+#define CKPT_COPY_SOPT(op, sk, name, opt) \
+ sock_cptrst_opt(op, sk->sk_socket, name, (char *)opt, sizeof(*opt))
+
+static int sock_cptrst_bufopts(int op, struct sock *sk,
+ struct ckpt_hdr_socket *h)
+{
+ if (CKPT_COPY_SOPT(op, sk, SO_RCVBUF, &h->sock.rcvbuf))
+ if ((op == CKPT_RST) &&
+ CKPT_COPY_SOPT(op, sk, SO_RCVBUFFORCE, &h->sock.rcvbuf)) {
+ ckpt_debug("Failed to set SO_RCVBUF");
+ return -EINVAL;
+ }
+
+ if (CKPT_COPY_SOPT(op, sk, SO_SNDBUF, &h->sock.sndbuf))
+ if ((op == CKPT_RST) &&
+ CKPT_COPY_SOPT(op, sk, SO_SNDBUFFORCE, &h->sock.sndbuf)) {
+ ckpt_debug("Failed to set SO_SNDBUF");
+ return -EINVAL;
+ }
+
+ /* It's silly that we have to fight ourselves here, but
+ * sock_setsockopt() doubles the initial value, so divide here
+ * to store the user's value and avoid doubling on restart
+ */
+ if ((op == CKPT_CPT) && (h->sock.rcvbuf != SOCK_MIN_RCVBUF))
+ h->sock.rcvbuf >>= 1;
+
+ if ((op == CKPT_CPT) && (h->sock.sndbuf != SOCK_MIN_SNDBUF))
+ h->sock.sndbuf >>= 1;
+
+ return 0;
+}
+
+struct sock_flag_mapping {
+ int opt;
+ int flag;
+};
+
+struct sock_flag_mapping sk_flag_map[] = {
+ {SO_OOBINLINE, SOCK_URGINLINE},
+ {SO_KEEPALIVE, SOCK_KEEPOPEN},
+ {SO_BROADCAST, SOCK_BROADCAST},
+ {SO_TIMESTAMP, SOCK_RCVTSTAMP},
+ {SO_TIMESTAMPNS, SOCK_RCVTSTAMPNS},
+ {SO_DEBUG, SOCK_DBG},
+ {SO_DONTROUTE, SOCK_LOCALROUTE},
+};
+
+struct sock_flag_mapping sock_flag_map[] = {
+ {SO_PASSCRED, SOCK_PASSCRED},
+};
+
+static int sock_restore_flag(struct socket *sock,
+ unsigned long *flags,
+ int flag,
+ int option)
+{
+ int v = 1;
+ int ret = 0;
+
+ if (test_and_clear_bit(flag, flags))
+ ret = sock_setsockopt(sock, SOL_SOCKET, option,
+ (char *)&v, sizeof(v));
+
+ return ret;
+}
+
+
+static int sock_restore_flags(struct socket *sock, struct ckpt_hdr_socket *h)
+{
+ unsigned long sk_flags = h->sock.flags;
+ unsigned long sock_flags = h->socket.flags;
+ int ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sk_flag_map); i++) {
+ int opt = sk_flag_map[i].opt;
+ int flag = sk_flag_map[i].flag;
+ ret = sock_restore_flag(sock, &sk_flags, flag, opt);
+ if (ret) {
+ ckpt_debug("Failed to set skopt %i: %i\n", opt, ret);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sock_flag_map); i++) {
+ int opt = sock_flag_map[i].opt;
+ int flag = sock_flag_map[i].flag;
+ ret = sock_restore_flag(sock, &sock_flags, flag, opt);
+ if (ret) {
+ ckpt_debug("Failed to set sockopt %i: %i\n", opt, ret);
+ return ret;
+ }
+ }
+
+ /* TODO: Handle SOCK_TIMESTAMPING_* flags */
+ if (test_bit(SOCK_TIMESTAMPING_TX_HARDWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_TX_SOFTWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_RX_HARDWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_RX_SOFTWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_SOFTWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_RAW_HARDWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_SYS_HARDWARE, &sk_flags)) {
+ ckpt_debug("SOF_TIMESTAMPING_* flags are not supported\n");
+ return -ENOSYS;
+ }
+
+ if (test_and_clear_bit(SOCK_DEAD, &sk_flags))
+ sock_set_flag(sock->sk, SOCK_DEAD);
+
+
+ /* Anything that is still set in the flags that isn't part of
+ * our protocol's default set, indicates an error
+ */
+ if (sk_flags & ~sock->sk->sk_flags) {
+ ckpt_debug("Unhandled sock flags: %lx\n", sk_flags);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sock_copy_timeval(int op, struct sock *sk,
+ int sockopt, __s64 *saved)
+{
+ struct timeval tv;
+
+ if (op == CKPT_CPT) {
+ if (CKPT_COPY_SOPT(op, sk, sockopt, &tv))
+ return -EINVAL;
+ *saved = timeval_to_ns(&tv);
+ } else {
+ tv = ns_to_timeval(*saved);
+ if (CKPT_COPY_SOPT(op, sk, sockopt, &tv))
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sock_cptrst(struct ckpt_ctx *ctx, struct sock *sk,
+ struct ckpt_hdr_socket *h, int op)
+{
+ if (sk->sk_socket)
+ CKPT_COPY(op, h->socket.state, sk->sk_socket->state);
+
+ CKPT_COPY(op, h->sock_common.bound_dev_if, sk->sk_bound_dev_if);
+ CKPT_COPY(op, h->sock_common.family, sk->sk_family);
+
+ CKPT_COPY(op, h->sock.shutdown, sk->sk_shutdown);
+ CKPT_COPY(op, h->sock.userlocks, sk->sk_userlocks);
+ CKPT_COPY(op, h->sock.no_check, sk->sk_no_check);
+ CKPT_COPY(op, h->sock.protocol, sk->sk_protocol);
+ CKPT_COPY(op, h->sock.err, sk->sk_err);
+ CKPT_COPY(op, h->sock.err_soft, sk->sk_err_soft);
+ CKPT_COPY(op, h->sock.type, sk->sk_type);
+ CKPT_COPY(op, h->sock.state, sk->sk_state);
+ CKPT_COPY(op, h->sock.backlog, sk->sk_max_ack_backlog);
+
+ if (sock_cptrst_bufopts(op, sk, h))
+ return -EINVAL;
+
+ if (CKPT_COPY_SOPT(op, sk, SO_REUSEADDR, &h->sock_common.reuse)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_REUSEADDR");
+
+ return -EINVAL;
+ }
+
+ if (CKPT_COPY_SOPT(op, sk, SO_PRIORITY, &h->sock.priority)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_PRIORITY");
+ return -EINVAL;
+ }
+
+ if (CKPT_COPY_SOPT(op, sk, SO_RCVLOWAT, &h->sock.rcvlowat)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_RCVLOWAT");
+ return -EINVAL;
+ }
+
+ if (CKPT_COPY_SOPT(op, sk, SO_LINGER, &h->sock.linger)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_LINGER");
+ return -EINVAL;
+ }
+
+ if (sock_copy_timeval(op, sk, SO_SNDTIMEO, &h->sock.sndtimeo)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_SNDTIMEO");
+ return -EINVAL;
+ }
+
+ if (sock_copy_timeval(op, sk, SO_RCVTIMEO, &h->sock.rcvtimeo)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_RCVTIMEO");
+ return -EINVAL;
+ }
+
+ if (op == CKPT_CPT) {
+ h->sock.flags = sk->sk_flags;
+ h->socket.flags = sk->sk_socket->flags;
+ } else {
+ int ret;
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ ret = sock_restore_flags(sk->sk_socket, h);
+ set_fs(old_fs);
+ if (ret)
+ return ret;
+ }
+
+ if ((h->socket.state == SS_CONNECTED) &&
+ (h->sock.state != TCP_ESTABLISHED)) {
+ ckpt_err(ctx, -EINVAL, "sock/et in inconsistent state: %i/%i",
+ h->socket.state, h->sock.state);
+ return -EINVAL;
+ } else if ((h->sock.state < TCP_ESTABLISHED) ||
+ (h->sock.state >= TCP_MAX_STATES)) {
+ ckpt_err(ctx, -EINVAL,
+ "sock in invalid state: %i", h->sock.state);
+ return -EINVAL;
+ } else if (h->socket.state > SS_DISCONNECTING) {
+ ckpt_err(ctx, -EINVAL, "socket in invalid state: %i",
+ h->socket.state);
+ return -EINVAL;
+ }
+
+ if (op == CKPT_RST)
+ return sock_cptrst_verify(h);
+ else
+ return 0;
+}
+
+static int __do_sock_checkpoint(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct socket *sock = sk->sk_socket;
+ struct ckpt_hdr_socket *h;
+ int ret;
+
+ if (!sock->ops->checkpoint) {
+ ckpt_err(ctx, -ENOSYS, "%(T)%(V)%(P)socket: proto_ops\n",
+ sock->ops, sock);
+ return -ENOSYS;
+ }
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_SOCKET);
+ if (!h)
+ return -ENOMEM;
+
+ /* part I: common to all sockets */
+ ret = sock_cptrst(ctx, sk, h, CKPT_CPT);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ if (ret < 0)
+ goto out;
+
+ /* part II: per socket type state */
+ ret = sock->ops->checkpoint(ctx, sock);
+ if (ret < 0)
+ goto out;
+
+ /* part III: socket buffers */
+ if ((sk->sk_state != TCP_LISTEN) && (!sock_flag(sk, SOCK_DEAD)))
+ ret = sock_defer_write_buffers(ctx, sk);
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+static int do_sock_checkpoint(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct socket *sock;
+ int ret;
+
+ if (sk->sk_socket)
+ return __do_sock_checkpoint(ctx, sk);
+
+ /* Temporarily adopt this orphan socket */
+ ret = sock_create(sk->sk_family, sk->sk_type, 0, &sock);
+ if (ret < 0)
+ return ret;
+ sock_graft(sk, sock);
+
+ ret = __do_sock_checkpoint(ctx, sk);
+
+ sock_orphan(sk);
+ sock->sk = NULL;
+ sock_release(sock);
+
+ return ret;
+}
+
+int checkpoint_sock(struct ckpt_ctx *ctx, void *ptr)
+{
+ return do_sock_checkpoint(ctx, (struct sock *)ptr);
+}
+
+int sock_file_checkpoint(struct ckpt_ctx *ctx, struct file *file)
+{
+ struct ckpt_hdr_file_socket *h;
+ struct socket *sock = file->private_data;
+ struct sock *sk = sock->sk;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_FILE);
+ if (!h)
+ return -ENOMEM;
+
+ h->common.f_type = CKPT_FILE_SOCKET;
+
+ h->sock_objref = checkpoint_obj(ctx, sk, CKPT_OBJ_SOCK);
+ if (h->sock_objref < 0) {
+ ret = h->sock_objref;
+ goto out;
+ }
+
+ ret = checkpoint_file_common(ctx, file, &h->common);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+static int sock_collect_skbs(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
+{
+ struct sk_buff_head tmpq;
+ struct sk_buff *skb;
+ int ret = 0;
+ int bytes;
+
+ skb_queue_head_init(&tmpq);
+
+ ret = sock_copy_buffers(queue, &tmpq, &bytes);
+ if (ret < 0)
+ return ret;
+
+ skb_queue_walk(&tmpq, skb) {
+ /* Socket buffers do not maintain a ref count on their
+ * owning sock because they're counted in sock_wmem_alloc.
+ * So, we only need to collect sockets from the queue that
+ * won't be collected any other way (i.e. DEAD sockets that
+ * are hanging around only because they're waiting for us
+ * to process their skb.
+ */
+
+ if (!ckpt_obj_lookup(ctx, skb->sk, CKPT_OBJ_SOCK) &&
+ sock_flag(skb->sk, SOCK_DEAD)) {
+ ret = ckpt_obj_collect(ctx, skb->sk, CKPT_OBJ_SOCK);
+ if (ret < 0)
+ break;
+ }
+ }
+
+ __skb_queue_purge(&tmpq);
+
+ return ret;
+}
+
+int sock_file_collect(struct ckpt_ctx *ctx, struct file *file)
+{
+ struct socket *sock = file->private_data;
+ struct sock *sk = sock->sk;
+ int ret;
+
+ ret = sock_collect_skbs(ctx, &sk->sk_write_queue);
+ if (ret < 0)
+ return ret;
+
+ ret = sock_collect_skbs(ctx, &sk->sk_receive_queue);
+ if (ret < 0)
+ return ret;
+
+ ret = ckpt_obj_collect(ctx, sk, CKPT_OBJ_SOCK);
+ if (ret < 0)
+ return ret;
+
+ if (sock->ops->collect)
+ ret = sock->ops->collect(ctx, sock);
+
+ return ret;
+}
+
+struct sock *do_sock_restore(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_socket *h;
+ struct socket *sock;
+ int ret;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET);
+ if (IS_ERR(h))
+ return ERR_PTR(PTR_ERR(h));
+
+ /* silently clear flags, e.g. SOCK_NONBLOCK or SOCK_CLOEXEC */
+ h->sock.type &= SOCK_TYPE_MASK;
+
+ ret = sock_create(h->sock_common.family, h->sock.type,
+ h->sock.protocol, &sock);
+ if (ret < 0)
+ goto err;
+
+ if (!sock->ops->restore) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "proto_ops lacks restore %pS\n", sock->ops);
+ goto err;
+ }
+
+ /*
+ * part II: per socket type state
+ * (also takes care of part III: socket buffer)
+ */
+ ret = sock->ops->restore(ctx, sock, h);
+ if (ret < 0)
+ goto err;
+
+ /* part I: common to all sockets */
+ ret = sock_cptrst(ctx, sock->sk, h, CKPT_RST);
+ if (ret < 0)
+ goto err;
+
+ ckpt_hdr_put(ctx, h);
+ return sock->sk;
+ err:
+ ckpt_hdr_put(ctx, h);
+ sock_release(sock);
+ return ERR_PTR(ret);
+}
+
+void *restore_sock(struct ckpt_ctx *ctx)
+{
+ return do_sock_restore(ctx);
+}
+
+struct file *sock_file_restore(struct ckpt_ctx *ctx, struct ckpt_hdr_file *ptr)
+{
+ struct ckpt_hdr_file_socket *h = (struct ckpt_hdr_file_socket *)ptr;
+ struct sock *sk;
+ struct file *file;
+ int fd, ret;
+
+ if (ptr->h.type != CKPT_HDR_FILE || ptr->f_type != CKPT_FILE_SOCKET)
+ return ERR_PTR(-EINVAL);
+
+ sk = ckpt_obj_fetch(ctx, h->sock_objref, CKPT_OBJ_SOCK);
+ if (IS_ERR(sk))
+ return ERR_PTR(PTR_ERR(sk));
+
+ fd = sock_alloc_file(sk->sk_socket, &file, O_RDWR);
+ if (fd < 0)
+ return ERR_PTR(fd);
+ put_unused_fd(fd); /* We'll let the checkpoint code re-allocate this */
+
+ /* Since objhash assumes the initial reference for a socket,
+ * we bump it here for this descriptor, unlike other places in
+ * the socket code which assume the descriptor is the owner.
+ */
+ sock_hold(sk);
+
+ ret = restore_file_common(ctx, file, ptr);
+ if (ret < 0) {
+ fput(file);
+ return ERR_PTR(ret);
+ }
+
+ return file;
+}
diff --git a/net/socket.c b/net/socket.c
index 4d4fdc2..3253c04 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -147,6 +147,10 @@ static const struct file_operations socket_file_ops = {
.sendpage = sock_sendpage,
.splice_write = generic_splice_sendpage,
.splice_read = sock_splice_read,
+#ifdef CONFIG_CHECKPOINT
+ .checkpoint = sock_file_checkpoint,
+ .collect = sock_file_collect,
+#endif
};
/*
@@ -342,7 +346,7 @@ static const struct dentry_operations sockfs_dentry_operations = {
* but we take care of internal coherence yet.
*/
-static int sock_alloc_file(struct socket *sock, struct file **f, int flags)
+int sock_alloc_file(struct socket *sock, struct file **f, int flags)
{
struct qstr name = { .name = "" };
struct path path;
diff --git a/net/unix/Makefile b/net/unix/Makefile
index b852a2b..fbff1e6 100644
--- a/net/unix/Makefile
+++ b/net/unix/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_UNIX) += unix.o
unix-y := af_unix.o garbage.o
unix-$(CONFIG_SYSCTL) += sysctl_net_unix.o
+unix-$(CONFIG_CHECKPOINT) += checkpoint.o
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f255119..dacba62 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -523,6 +523,9 @@ static const struct proto_ops unix_stream_ops = {
.recvmsg = unix_stream_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
+ .checkpoint = unix_checkpoint,
+ .restore = unix_restore,
+ .collect = unix_collect,
};
static const struct proto_ops unix_dgram_ops = {
@@ -544,6 +547,9 @@ static const struct proto_ops unix_dgram_ops = {
.recvmsg = unix_dgram_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
+ .checkpoint = unix_checkpoint,
+ .restore = unix_restore,
+ .collect = unix_collect,
};
static const struct proto_ops unix_seqpacket_ops = {
@@ -565,6 +571,9 @@ static const struct proto_ops unix_seqpacket_ops = {
.recvmsg = unix_dgram_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
+ .checkpoint = unix_checkpoint,
+ .restore = unix_restore,
+ .collect = unix_collect,
};
static struct proto unix_proto = {
diff --git a/net/unix/checkpoint.c b/net/unix/checkpoint.c
new file mode 100644
index 0000000..90415b0
--- /dev/null
+++ b/net/unix/checkpoint.c
@@ -0,0 +1,647 @@
+#include <linux/namei.h>
+#include <linux/file.h>
+#include <linux/fs_struct.h>
+#include <linux/deferqueue.h>
+#include <linux/checkpoint.h>
+#include <linux/checkpoint_hdr.h>
+#include <linux/user.h>
+#include <net/af_unix.h>
+#include <net/tcp_states.h>
+
+struct dq_join {
+ struct ckpt_ctx *ctx;
+ int src_objref;
+ int dst_objref;
+};
+
+struct dq_buffers {
+ struct ckpt_ctx *ctx;
+ int sk_objref; /* objref of the socket these buffers belong to */
+};
+
+#define UNIX_ADDR_EMPTY(a) (a <= sizeof(short))
+
+static inline int unix_need_cwd(struct sockaddr_un *addr, unsigned long len)
+{
+ return (!UNIX_ADDR_EMPTY(len)) &&
+ addr->sun_path[0] &&
+ (addr->sun_path[0] != '/');
+}
+
+static int unix_join(struct sock *src, struct sock *dst)
+{
+ if (unix_sk(src)->peer != NULL)
+ return 0; /* We're second */
+
+ sock_hold(dst);
+ unix_sk(src)->peer = dst;
+
+ return 0;
+
+}
+
+static int unix_deferred_join(void *data)
+{
+ struct dq_join *dq = (struct dq_join *)data;
+ struct ckpt_ctx *ctx = dq->ctx;
+ struct sock *src;
+ struct sock *dst;
+
+ src = ckpt_obj_fetch(ctx, dq->src_objref, CKPT_OBJ_SOCK);
+ if (!src) {
+ ckpt_err(ctx, -EINVAL, "%(O)Bad src sock\n", dq->src_objref);
+ return -EINVAL;
+ }
+
+ dst = ckpt_obj_fetch(ctx, dq->dst_objref, CKPT_OBJ_SOCK);
+ if (!dst) {
+ ckpt_err(ctx, -EINVAL, "%(O)Bad dst sock\n", dq->dst_objref);
+ return -EINVAL;
+ }
+
+ return unix_join(src, dst);
+}
+
+static int unix_defer_join(struct ckpt_ctx *ctx,
+ int src_objref,
+ int dst_objref)
+{
+ struct dq_join dq;
+
+ dq.ctx = ctx;
+ dq.src_objref = src_objref;
+ dq.dst_objref = dst_objref;
+
+ /* NB: This is safe to do inside deferqueue_run() since it uses
+ * list_for_each_safe()
+ */
+ return deferqueue_add(ctx->files_deferq, &dq, sizeof(dq),
+ unix_deferred_join, NULL);
+}
+
+static int unix_write_cwd(struct ckpt_ctx *ctx,
+ struct sock *sk, const char *sockpath)
+{
+ struct path path;
+ char *buf;
+ char *fqpath;
+ int offset;
+ int len = PATH_MAX;
+ int ret = -ENOENT;
+
+ buf = kmalloc(len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ path.dentry = unix_sk(sk)->dentry;
+ path.mnt = unix_sk(sk)->mnt;
+
+ fqpath = ckpt_fill_fname(&path, &ctx->root_fs_path, buf, &len);
+ if (IS_ERR(fqpath)) {
+ ret = PTR_ERR(fqpath);
+ goto out;
+ }
+
+ offset = strlen(fqpath) - strlen(sockpath);
+ if (offset <= 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ fqpath[offset] = '\0';
+
+ ckpt_debug("writing socket directory: %s\n", fqpath);
+ ret = ckpt_write_string(ctx, fqpath, offset + 1);
+ out:
+ kfree(buf);
+ return ret;
+}
+
+int unix_checkpoint(struct ckpt_ctx *ctx, struct socket *sock)
+{
+ struct unix_sock *sk = unix_sk(sock->sk);
+ struct ckpt_hdr_socket_unix *un;
+ int new;
+ int ret = -ENOMEM;
+
+ if ((sock->sk->sk_state == TCP_LISTEN) &&
+ !skb_queue_empty(&sock->sk->sk_receive_queue)) {
+ ckpt_err(ctx, -EBUSY,
+ "%(T)%(E)%(P)af_unix: listen with pending peers\n",
+ sock);
+ return -EBUSY;
+ }
+
+ un = ckpt_hdr_get_type(ctx, sizeof(*un), CKPT_HDR_SOCKET_UNIX);
+ if (!un)
+ return -EINVAL;
+
+ ret = ckpt_sock_getnames(ctx, sock,
+ (struct sockaddr *)&un->laddr, &un->laddr_len,
+ (struct sockaddr *)&un->raddr, &un->raddr_len);
+ if (ret)
+ goto out;
+
+ if (sk->dentry && (sk->dentry->d_inode->i_nlink > 0))
+ un->flags |= CKPT_UNIX_LINKED;
+
+ un->this = ckpt_obj_lookup_add(ctx, sk, CKPT_OBJ_SOCK, &new);
+ if (un->this < 0)
+ goto out;
+
+ if (sk->peer)
+ un->peer = checkpoint_obj(ctx, sk->peer, CKPT_OBJ_SOCK);
+ else
+ un->peer = 0;
+
+ if (un->peer < 0) {
+ ret = un->peer;
+ goto out;
+ }
+
+ un->peercred_uid = sock->sk->sk_peercred.uid;
+ un->peercred_gid = sock->sk->sk_peercred.gid;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) un);
+ if (ret < 0)
+ goto out;
+
+ if (unix_need_cwd(&un->laddr, un->laddr_len))
+ ret = unix_write_cwd(ctx, sock->sk, un->laddr.sun_path);
+ out:
+ ckpt_hdr_put(ctx, un);
+
+ return ret;
+}
+
+int unix_collect(struct ckpt_ctx *ctx, struct socket *sock)
+{
+ struct unix_sock *sk = unix_sk(sock->sk);
+ int ret;
+
+ ret = ckpt_obj_collect(ctx, sock->sk, CKPT_OBJ_SOCK);
+ if (ret < 0)
+ return ret;
+
+ if (sk->peer)
+ ret = ckpt_obj_collect(ctx, sk->peer, CKPT_OBJ_SOCK);
+
+ return 0;
+}
+
+static int sock_read_buffer_sendmsg(struct ckpt_ctx *ctx,
+ struct sockaddr *addr,
+ unsigned int addrlen)
+{
+ struct ckpt_hdr_socket_buffer *h;
+ struct sock *sk;
+ struct msghdr msg;
+ struct kvec kvec;
+ uint8_t sock_shutdown;
+ uint8_t peer_shutdown = 0;
+ void *buf = NULL;
+ int sndbuf;
+ int ret;
+
+ memset(&msg, 0, sizeof(msg));
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_BUFFER);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ ret = -EINVAL;
+ if (h->lin_len > SKB_MAX_ALLOC) {
+ ckpt_err(ctx, ret, "socket buffer too big (%u > %lu)\n",
+ h->lin_len, SKB_MAX_ALLOC);
+ goto out;
+ } else if (h->nr_frags != 0) {
+ ckpt_err(ctx, ret, "unix socket claims to have fragments\n");
+ goto out;
+ }
+
+ buf = kmalloc(h->lin_len, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ kvec.iov_len = h->lin_len;
+ kvec.iov_base = buf;
+ ret = _ckpt_read_obj_type(ctx, kvec.iov_base,
+ h->lin_len, CKPT_HDR_BUFFER);
+ ckpt_debug("read unix socket buffer %u: %i\n", h->lin_len, ret);
+ if (ret < h->lin_len) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ sk = ckpt_obj_fetch(ctx, h->sk_objref, CKPT_OBJ_SOCK);
+ if (IS_ERR(sk)) {
+ ret = PTR_ERR(sk);
+ goto out;
+ }
+
+ /* If we don't have a destination or a peer and we know the
+ * destination of this skb, then we must need to join with our
+ * peer
+ */
+ if (!addrlen && !unix_sk(sk)->peer) {
+ struct sock *pr;
+ pr = ckpt_obj_fetch(ctx, h->pr_objref, CKPT_OBJ_SOCK);
+ if (IS_ERR(pr)) {
+ ret = PTR_ERR(pr);
+ ckpt_err(ctx, ret, "Failed to fetch peer\n");
+ goto out;
+ }
+ ret = unix_join(sk, pr);
+ if (ret < 0) {
+ ckpt_err(ctx, ret, "Failed to join sockets\n");
+ goto out;
+ }
+ }
+
+ msg.msg_name = addr;
+ msg.msg_namelen = addrlen;
+
+ /* If peer is shutdown, unshutdown it for this process */
+ sock_shutdown = sk->sk_shutdown;
+ sk->sk_shutdown &= ~SHUTDOWN_MASK;
+
+ /* Unshutdown peer too, if necessary */
+ if (unix_sk(sk)->peer) {
+ peer_shutdown = unix_sk(sk)->peer->sk_shutdown;
+ unix_sk(sk)->peer->sk_shutdown &= ~SHUTDOWN_MASK;
+ }
+
+ /* Make sure there's room in the send buffer: Worst case, we
+ * give them the benefit of the doubt and set the buffer limit
+ * to the system default. This should cover the case where
+ * the user set the limit low after loading up the buffer.
+ *
+ * However, if there isn't room in the buffer and the system
+ * default won't accommodate them either, then increase the
+ * limit as needed, only if they have CAP_NET_ADMIN.
+ */
+ sndbuf = sk->sk_sndbuf;
+ if (((sk->sk_sndbuf - atomic_read(&sk->sk_wmem_alloc)) < h->lin_len) &&
+ (h->lin_len > sysctl_wmem_max) &&
+ capable(CAP_NET_ADMIN))
+ sk->sk_sndbuf += h->lin_len;
+ else
+ sk->sk_sndbuf = sysctl_wmem_max;
+
+ ret = kernel_sendmsg(sk->sk_socket, &msg, &kvec, 1, h->lin_len);
+ ckpt_debug("kernel_sendmsg(%i,%u): %i\n",
+ h->sk_objref, h->lin_len, ret);
+ if ((ret > 0) && (ret != h->lin_len))
+ ret = -ENOMEM;
+
+ sk->sk_sndbuf = sndbuf;
+ sk->sk_shutdown = sock_shutdown;
+ if (peer_shutdown)
+ unix_sk(sk)->peer->sk_shutdown = peer_shutdown;
+ out:
+ ckpt_hdr_put(ctx, h);
+ kfree(buf);
+ return ret;
+}
+
+static int unix_read_buffers(struct ckpt_ctx *ctx,
+ struct sockaddr *addr,
+ unsigned int addrlen)
+{
+ struct ckpt_hdr_socket_queue *h;
+ int ret = 0;
+ int i;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_QUEUE);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ for (i = 0; i < h->skb_count; i++) {
+ ret = sock_read_buffer_sendmsg(ctx, addr, addrlen);
+ ckpt_debug("read_buffer_sendmsg(%i): %i\n", i, ret);
+ if (ret < 0)
+ goto out;
+
+ if (ret > h->total_bytes) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "Buffers exceeded claim");
+ goto out;
+ }
+
+ h->total_bytes -= ret;
+ }
+
+ ret = h->skb_count;
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+static int unix_deferred_restore_buffers(void *data)
+{
+ struct dq_buffers *dq = (struct dq_buffers *)data;
+ struct ckpt_ctx *ctx = dq->ctx;
+ struct sock *sk;
+ struct sockaddr *addr = NULL;
+ unsigned int addrlen = 0;
+ int ret;
+
+ sk = ckpt_obj_fetch(ctx, dq->sk_objref, CKPT_OBJ_SOCK);
+ if (!sk) {
+ ckpt_err(ctx, -EINVAL, "%(O) missing sock\n", dq->sk_objref);
+ return -EINVAL;
+ }
+
+ if ((sk->sk_type == SOCK_DGRAM) && (unix_sk(sk)->addr != NULL)) {
+ addr = (struct sockaddr *)&unix_sk(sk)->addr->name;
+ addrlen = unix_sk(sk)->addr->len;
+ }
+
+ ret = unix_read_buffers(ctx, addr, addrlen);
+ ckpt_debug("read recv buffers: %i\n", ret);
+ if (ret < 0)
+ return ret;
+
+ ret = unix_read_buffers(ctx, addr, addrlen);
+ ckpt_debug("read send buffers: %i\n", ret);
+ if (ret > 0)
+ ret = -EINVAL; /* No send buffers for UNIX sockets */
+
+ return ret;
+}
+
+static int unix_defer_restore_buffers(struct ckpt_ctx *ctx, int sk_objref)
+{
+ struct dq_buffers dq;
+
+ dq.ctx = ctx;
+ dq.sk_objref = sk_objref;
+
+ /* NB: This is safe to do inside deferqueue_run() since it uses
+ * list_for_each_safe()
+ */
+ return deferqueue_add(ctx->files_deferq, &dq, sizeof(dq),
+ unix_deferred_restore_buffers, NULL);
+}
+
+static struct unix_address *unix_makeaddr(struct sockaddr_un *sun_addr,
+ unsigned len)
+{
+ struct unix_address *addr;
+
+ if (len > sizeof(struct sockaddr_un))
+ return ERR_PTR(-EINVAL);
+
+ addr = kmalloc(sizeof(*addr) + len, GFP_KERNEL);
+ if (!addr)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(addr->name, sun_addr, len);
+ addr->len = len;
+ atomic_set(&addr->refcnt, 1);
+
+ return addr;
+}
+
+static int unix_restore_connected(struct ckpt_ctx *ctx,
+ struct ckpt_hdr_socket *h,
+ struct ckpt_hdr_socket_unix *un,
+ struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr *addr = NULL;
+ unsigned long flags = h->sock.flags;
+ unsigned int addrlen = 0;
+ int dead = test_bit(SOCK_DEAD, &flags);
+ int ret = 0;
+
+
+ if (un->peer == 0) {
+ /* These get propagated to the msghdr, so only set them
+ * if we're not connected to a peer, else we'll get an error
+ * when we sendmsg()
+ */
+ addr = (struct sockaddr *)&un->laddr;
+ addrlen = un->laddr_len;
+ }
+
+ sk->sk_peercred.pid = task_tgid_vnr(current);
+
+ if (may_setuid(ctx->realcred->user->user_ns, un->peercred_uid) &&
+ may_setgid(un->peercred_gid)) {
+ sk->sk_peercred.uid = un->peercred_uid;
+ sk->sk_peercred.gid = un->peercred_gid;
+ } else {
+ ckpt_err(ctx, -EPERM, "peercred %i:%i would require setuid",
+ un->peercred_uid, un->peercred_gid);
+ return -EPERM;
+ }
+
+ if (!dead && (un->peer > 0)) {
+ ret = unix_defer_join(ctx, un->this, un->peer);
+ ckpt_debug("unix_defer_join: %i\n", ret);
+ }
+
+ if (!dead && !ret)
+ ret = unix_defer_restore_buffers(ctx, un->this);
+
+ return ret;
+}
+
+static int unix_unlink(const char *name)
+{
+ struct path spath;
+ struct path ppath;
+ int ret;
+
+ ret = kern_path(name, 0, &spath);
+ if (ret)
+ return ret;
+
+ ret = kern_path(name, LOOKUP_PARENT, &ppath);
+ if (ret)
+ goto out_s;
+
+ if (!spath.dentry) {
+ ckpt_debug("No dentry found for %s\n", name);
+ ret = -ENOENT;
+ goto out_p;
+ }
+
+ if (!ppath.dentry || !ppath.dentry->d_inode) {
+ ckpt_debug("No inode for parent of %s\n", name);
+ ret = -ENOENT;
+ goto out_p;
+ }
+
+ ret = vfs_unlink(ppath.dentry->d_inode, spath.dentry);
+ out_p:
+ path_put(&ppath);
+ out_s:
+ path_put(&spath);
+
+ return ret;
+}
+
+/* Call bind() for socket, optionally changing (temporarily) to @path first
+ * if non-NULL
+ */
+static int unix_chdir_and_bind(struct socket *sock,
+ const char *path,
+ struct sockaddr *addr,
+ unsigned long addrlen)
+{
+ struct sockaddr_un *un = (struct sockaddr_un *)addr;
+ struct path cur = { .mnt = NULL, .dentry = NULL };
+ struct path dir = { .mnt = NULL, .dentry = NULL };
+ int ret;
+
+ if (path) {
+ ckpt_debug("switching to cwd %s for unix bind", path);
+
+ ret = kern_path(path, 0, &dir);
+ if (ret)
+ return ret;
+
+ ret = inode_permission(dir.dentry->d_inode,
+ MAY_EXEC | MAY_ACCESS);
+ if (ret)
+ goto out;
+
+ write_lock(¤t->fs->lock);
+ cur = current->fs->pwd;
+ current->fs->pwd = dir;
+ write_unlock(¤t->fs->lock);
+ }
+
+ ret = unix_unlink(un->sun_path);
+ ckpt_debug("unlink(%s): %i\n", un->sun_path, ret);
+ if ((ret == 0) || (ret == -ENOENT))
+ ret = sock_bind(sock, addr, addrlen);
+
+ if (path) {
+ write_lock(¤t->fs->lock);
+ current->fs->pwd = cur;
+ write_unlock(¤t->fs->lock);
+ }
+ out:
+ if (path)
+ path_put(&dir);
+
+ return ret;
+}
+
+static int unix_fakebind(struct socket *sock,
+ struct sockaddr_un *addr, unsigned long len)
+{
+ struct unix_address *uaddr;
+
+ uaddr = unix_makeaddr(addr, len);
+ if (IS_ERR(uaddr))
+ return PTR_ERR(uaddr);
+
+ unix_sk(sock->sk)->addr = uaddr;
+
+ return 0;
+}
+
+static int unix_restore_bind(struct ckpt_hdr_socket *h,
+ struct ckpt_hdr_socket_unix *un,
+ struct socket *sock,
+ const char *path)
+{
+ struct sockaddr *addr = (struct sockaddr *)&un->laddr;
+ unsigned long len = un->laddr_len;
+ unsigned long flags = h->sock.flags;
+ int dead = test_bit(SOCK_DEAD, &flags);
+
+ if (dead)
+ return unix_fakebind(sock, &un->laddr, len);
+ else if (!un->laddr.sun_path[0])
+ return sock_bind(sock, addr, len);
+ else if (!(un->flags & CKPT_UNIX_LINKED))
+ return unix_fakebind(sock, &un->laddr, len);
+ else
+ return unix_chdir_and_bind(sock, path, addr, len);
+}
+
+/* Some easy pre-flight checks before we get underway */
+static int unix_precheck(struct socket *sock, struct ckpt_hdr_socket *h)
+{
+ struct net *net = sock_net(sock->sk);
+ unsigned long sk_flags = h->sock.flags;
+
+ if ((h->socket.state == SS_CONNECTING) ||
+ (h->socket.state == SS_DISCONNECTING) ||
+ (h->socket.state == SS_FREE)) {
+ ckpt_debug("AF_UNIX socket can't be SS_(DIS)CONNECTING");
+ return -EINVAL;
+ }
+
+ /* AF_UNIX overloads the backlog setting to define the maximum
+ * queue length for DGRAM sockets. Make sure we don't let the
+ * caller exceed that value on restart.
+ */
+ if ((h->sock.type == SOCK_DGRAM) &&
+ (h->sock.backlog > net->unx.sysctl_max_dgram_qlen)) {
+ ckpt_debug("DGRAM backlog of %i exceeds system max of %i\n",
+ h->sock.backlog, net->unx.sysctl_max_dgram_qlen);
+ return -EINVAL;
+ }
+
+ if (test_bit(SOCK_USE_WRITE_QUEUE, &sk_flags)) {
+ ckpt_debug("AF_UNIX socket has SOCK_USE_WRITE_QUEUE set");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int unix_restore(struct ckpt_ctx *ctx, struct socket *sock,
+ struct ckpt_hdr_socket *h)
+
+{
+ struct ckpt_hdr_socket_unix *un;
+ int ret = -EINVAL;
+ char *cwd = NULL;
+
+ ret = unix_precheck(sock, h);
+ if (ret)
+ return ret;
+
+ un = ckpt_read_obj_type(ctx, sizeof(*un), CKPT_HDR_SOCKET_UNIX);
+ if (IS_ERR(un))
+ return PTR_ERR(un);
+
+ if (un->peer < 0)
+ goto out;
+
+ if (unix_need_cwd(&un->laddr, un->laddr_len)) {
+ cwd = ckpt_read_string(ctx, PATH_MAX);
+ if (IS_ERR(cwd)) {
+ ret = PTR_ERR(cwd);
+ goto out;
+ }
+ }
+
+ if ((h->sock.state != TCP_ESTABLISHED) &&
+ !UNIX_ADDR_EMPTY(un->laddr_len)) {
+ ret = unix_restore_bind(h, un, sock, cwd);
+ if (ret)
+ goto out;
+ }
+
+ if ((h->sock.state == TCP_ESTABLISHED) || (h->sock.state == TCP_CLOSE))
+ ret = unix_restore_connected(ctx, h, un, sock);
+ else if (h->sock.state == TCP_LISTEN)
+ ret = sock->ops->listen(sock, h->sock.backlog);
+ else
+ ckpt_err(ctx, ret, "bad af_unix state %i\n", h->sock.state);
+
+ out:
+ ckpt_hdr_put(ctx, un);
+ kfree(cwd);
+ return ret;
+}
--
1.6.3.3
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply related
* [C/R v20][PATCH 78/96] c/r: add support for connected INET sockets (v5)
From: Oren Laadan @ 2010-03-17 16:09 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-kernel, linux-mm, linux-api, Serge Hallyn, Ingo Molnar,
containers, Dan Smith, netdev
In-Reply-To: <1268842164-5590-78-git-send-email-orenl@cs.columbia.edu>
From: Dan Smith <danms@us.ibm.com>
This patch adds basic support for C/R of open INET sockets. I think that
all the important bits of the TCP and ICSK socket structures is saved,
but I think there is still some additional IPv6 stuff that needs to be
handled.
With this patch applied, the following script can be used to demonstrate
the functionality:
https://lists.linux-foundation.org/pipermail/containers/2009-October/021239.html
It shows that this enables migration of a sendmail process with open
connections from one machine to another without dropping.
We probably need comments from the netdev people about the quality of
sanity checking we do on the values in the ckpt_hdr_socket_inet
structure on restart.
Note that this still doesn't address lingering sockets yet.
Changelog [v19-rc3]:
- Rebase to kernel 2.6.33 (add 'inet_' prefix to some sk fields)
- Relax tcp.window_clamp value in INET restore
Changelog [v19-rc2]:
- Restore gso_type fields on sockets and buffers, so that they're
properly handled on incoming path. Use the proper value from the
socket (instead of storing that per-buffer) to avoid needing to
detect (e.g.) the user restore a UDP buffer into a TCP socket.
Changes in v5:
- Change ckpt_write_err() to ckpt_err()
Changes in v4:
- Use the new socket buffer restore functions introduced in the
previous patch
- Move listen_sockets list under the restart items in ckpt_ctx
- Rename RESTART_SOCK_LISTENONLY to RESTART_CONN_RESET
Changes in v3:
- Prevent restart from allowing a bind on a <1024 port unless the
user is granted that capability
- Add some sanity checking in the inet_precheck() function to make sure
the values read from the checkpoint image are within acceptable ranges
- Check the result of sock_restore_header_info() and fail if needed
Changes in v2:
- Restore saddr, rcv_saddr, daddr, sport, and dport from the sockaddr
structure instead of saving them separately
- Fix 'sock' naming in sock_cptrst()
- Don't take the queue lock before skb_queue_tail() since it is
done for us
- Allow "listen only" restore behavior if RESTART_SOCK_LISTENONLY
flag is specified on sys_restart()
- Pull the implementation of the list of listening sockets back into
this patch
- Fix dangling printk
- Add some comments around the parent/child restore logic
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: Oren Laadan <orenl@librato.com>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Tested-by: Serge E. Hallyn <serue@us.ibm.com>
---
checkpoint/sys.c | 4 +
include/linux/checkpoint.h | 7 +-
include/linux/checkpoint_hdr.h | 95 ++++++++++
include/linux/checkpoint_types.h | 1 +
net/checkpoint.c | 19 +-
net/ipv4/checkpoint.c | 373 +++++++++++++++++++++++++++++++++++++-
6 files changed, 481 insertions(+), 18 deletions(-)
diff --git a/checkpoint/sys.c b/checkpoint/sys.c
index 02b12a3..62f49ad 100644
--- a/checkpoint/sys.c
+++ b/checkpoint/sys.c
@@ -236,6 +236,8 @@ static void ckpt_ctx_free(struct ckpt_ctx *ctx)
kfree(ctx->pids_arr);
+ sock_listening_list_free(&ctx->listen_sockets);
+
kfree(ctx);
}
@@ -269,6 +271,8 @@ static struct ckpt_ctx *ckpt_ctx_alloc(int fd, unsigned long uflags,
mutex_init(&ctx->msg_mutex);
+ INIT_LIST_HEAD(&ctx->listen_sockets);
+
err = -EBADF;
ctx->file = fget(fd);
if (!ctx->file)
diff --git a/include/linux/checkpoint.h b/include/linux/checkpoint.h
index e0f4bd1..57b8fd0 100644
--- a/include/linux/checkpoint.h
+++ b/include/linux/checkpoint.h
@@ -19,6 +19,7 @@
#define RESTART_TASKSELF 0x1
#define RESTART_FROZEN 0x2
#define RESTART_GHOST 0x4
+#define RESTART_CONN_RESET 0x10
/* misc user visible */
#define CHECKPOINT_FD_NONE -1
@@ -57,7 +58,8 @@ extern long do_sys_restart(pid_t pid, int fd,
#define RESTART_USER_FLAGS \
(RESTART_TASKSELF | \
RESTART_FROZEN | \
- RESTART_GHOST)
+ RESTART_GHOST | \
+ RESTART_CONN_RESET)
extern int walk_task_subtree(struct task_struct *task,
int (*func)(struct task_struct *, void *),
@@ -103,7 +105,8 @@ extern int ckpt_sock_getnames(struct ckpt_ctx *ctx,
struct socket *socket,
struct sockaddr *loc, unsigned *loc_len,
struct sockaddr *rem, unsigned *rem_len);
-extern struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx);
+extern struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx, struct sock *sk);
+extern void sock_listening_list_free(struct list_head *head);
/* ckpt kflags */
#define ckpt_set_ctx_kflag(__ctx, __kflag) \
diff --git a/include/linux/checkpoint_hdr.h b/include/linux/checkpoint_hdr.h
index cf36fe1..1a6343a 100644
--- a/include/linux/checkpoint_hdr.h
+++ b/include/linux/checkpoint_hdr.h
@@ -15,6 +15,7 @@
#include <linux/socket.h>
#include <linux/un.h>
#include <linux/in.h>
+#include <linux/in6.h>
#else
#include <sys/types.h>
#include <linux/types.h>
@@ -576,6 +577,100 @@ struct ckpt_hdr_socket_unix {
struct ckpt_hdr_socket_inet {
struct ckpt_hdr h;
+ __u32 daddr;
+ __u32 rcv_saddr;
+ __u32 saddr;
+ __u16 dport;
+ __u16 num;
+ __u16 sport;
+ __s16 uc_ttl;
+ __u16 cmsg_flags;
+
+ struct {
+ __u64 timeout;
+ __u32 ato;
+ __u32 lrcvtime;
+ __u16 last_seg_size;
+ __u16 rcv_mss;
+ __u8 pending;
+ __u8 quick;
+ __u8 pingpong;
+ __u8 blocked;
+ } icsk_ack __attribute__ ((aligned(8)));
+
+ /* FIXME: Skipped opt, tos, multicast, cork settings */
+
+ struct {
+ __u32 rcv_nxt;
+ __u32 copied_seq;
+ __u32 rcv_wup;
+ __u32 snd_nxt;
+ __u32 snd_una;
+ __u32 snd_sml;
+ __u32 rcv_tstamp;
+ __u32 lsndtime;
+
+ __u32 snd_wl1;
+ __u32 snd_wnd;
+ __u32 max_window;
+ __u32 mss_cache;
+ __u32 window_clamp;
+ __u32 rcv_ssthresh;
+ __u32 frto_highmark;
+
+ __u32 srtt;
+ __u32 mdev;
+ __u32 mdev_max;
+ __u32 rttvar;
+ __u32 rtt_seq;
+
+ __u32 packets_out;
+ __u32 retrans_out;
+
+ __u32 snd_up;
+ __u32 rcv_wnd;
+ __u32 write_seq;
+ __u32 pushed_seq;
+ __u32 lost_out;
+ __u32 sacked_out;
+ __u32 fackets_out;
+ __u32 tso_deferred;
+ __u32 bytes_acked;
+
+ __s32 lost_cnt_hint;
+ __u32 retransmit_high;
+
+ __u32 lost_retrans_low;
+
+ __u32 prior_ssthresh;
+ __u32 high_seq;
+
+ __u32 retrans_stamp;
+ __u32 undo_marker;
+ __s32 undo_retrans;
+ __u32 total_retrans;
+
+ __u32 urg_seq;
+ __u32 keepalive_time;
+ __u32 keepalive_intvl;
+
+ __u16 urg_data;
+ __u16 advmss;
+ __u8 frto_counter;
+ __u8 nonagle;
+
+ __u8 ecn_flags;
+ __u8 reordering;
+
+ __u8 keepalive_probes;
+ } tcp __attribute__ ((aligned(8)));
+
+ struct {
+ struct in6_addr saddr;
+ struct in6_addr rcv_saddr;
+ struct in6_addr daddr;
+ } inet6 __attribute__ ((aligned(8)));
+
__u32 laddr_len;
__u32 raddr_len;
struct sockaddr_in laddr;
diff --git a/include/linux/checkpoint_types.h b/include/linux/checkpoint_types.h
index 6edcaea..75e198f 100644
--- a/include/linux/checkpoint_types.h
+++ b/include/linux/checkpoint_types.h
@@ -79,6 +79,7 @@ struct ckpt_ctx {
wait_queue_head_t waitq; /* waitqueue for restarting tasks */
wait_queue_head_t ghostq; /* waitqueue for ghost tasks */
struct cred *realcred, *ecred; /* tmp storage for cred at restart */
+ struct list_head listen_sockets;/* listening parent sockets */
struct ckpt_stats stats; /* statistics */
diff --git a/net/checkpoint.c b/net/checkpoint.c
index 6fe7aa2..0eb8860 100644
--- a/net/checkpoint.c
+++ b/net/checkpoint.c
@@ -116,9 +116,9 @@ static void sock_record_header_info(struct sk_buff *skb,
h->nr_frags = skb_shinfo(skb)->nr_frags;
}
-int sock_restore_header_info(struct ckpt_ctx *ctx,
- struct sk_buff *skb,
- struct ckpt_hdr_socket_buffer *h)
+static int sock_restore_header_info(struct ckpt_ctx *ctx,
+ struct sock *sk, struct sk_buff *skb,
+ struct ckpt_hdr_socket_buffer *h)
{
if (h->mac_header + h->mac_len != h->network_header) {
ckpt_err(ctx, -EINVAL,
@@ -172,6 +172,8 @@ int sock_restore_header_info(struct ckpt_ctx *ctx,
skb->data = skb->head + h->data_offset;
skb->len = h->skb_len;
+ skb_shinfo(skb)->gso_type = sk->sk_gso_type;
+
return 0;
}
@@ -217,7 +219,7 @@ static int sock_restore_skb_frag(struct ckpt_ctx *ctx,
return ret;
}
-struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx)
+struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx, struct sock *sk)
{
struct ckpt_hdr_socket_buffer *h;
struct sk_buff *skb = NULL;
@@ -270,7 +272,7 @@ struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx)
goto out;
}
- sock_restore_header_info(ctx, skb, h);
+ sock_restore_header_info(ctx, sk, skb, h);
out:
ckpt_hdr_put(ctx, h);
if (ret < 0) {
@@ -936,10 +938,9 @@ struct sock *do_sock_restore(struct ckpt_ctx *ctx)
goto err;
if ((h->sock_common.family == AF_INET) &&
- (h->sock.state != TCP_LISTEN)) {
- /* Temporary hack to enable restore of TCP_LISTEN sockets
- * while forcing anything else to a closed state
- */
+ (h->sock.state != TCP_LISTEN) &&
+ (ctx->uflags & RESTART_CONN_RESET)) {
+ ckpt_debug("Forcing open socket closed\n");
sock->sk->sk_state = TCP_CLOSE;
sock->state = SS_UNCONNECTED;
}
diff --git a/net/ipv4/checkpoint.c b/net/ipv4/checkpoint.c
index 1982119..b4024e7 100644
--- a/net/ipv4/checkpoint.c
+++ b/net/ipv4/checkpoint.c
@@ -17,6 +17,7 @@
#include <linux/deferqueue.h>
#include <net/tcp_states.h>
#include <net/tcp.h>
+#include <net/ipv6.h>
struct dq_sock {
struct ckpt_ctx *ctx;
@@ -28,6 +29,248 @@ struct dq_buffers {
struct sock *sk;
};
+struct listen_item {
+ struct sock *sk;
+ struct list_head list;
+};
+
+void sock_listening_list_free(struct list_head *head)
+{
+ struct listen_item *item, *tmp;
+
+ list_for_each_entry_safe(item, tmp, head, list) {
+ list_del(&item->list);
+ kfree(item);
+ }
+}
+
+static int sock_listening_list_add(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct listen_item *item;
+
+ item = kmalloc(sizeof(*item), GFP_KERNEL);
+ if (!item)
+ return -ENOMEM;
+
+ item->sk = sk;
+ list_add(&item->list, &ctx->listen_sockets);
+
+ return 0;
+}
+
+static struct sock *sock_get_parent(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct listen_item *item;
+
+ list_for_each_entry(item, &ctx->listen_sockets, list) {
+ if (inet_sk(sk)->inet_sport == inet_sk(item->sk)->inet_sport)
+ return item->sk;
+ }
+
+ return NULL;
+}
+
+static int sock_hash_parent(void *data)
+{
+ struct dq_sock *dq = (struct dq_sock *)data;
+ struct sock *parent;
+
+ ckpt_debug("INET post-restart hash\n");
+
+ dq->sk->sk_prot->hash(dq->sk);
+
+ /* If there is a listening socket with the same source port,
+ * then become a child of that socket [we are the result of an
+ * accept()]. Otherwise hash ourselves directly in [we are
+ * the result of a connect()]
+ */
+
+ parent = sock_get_parent(dq->ctx, dq->sk);
+ if (parent) {
+ inet_sk(dq->sk)->inet_num = ntohs(inet_sk(dq->sk)->inet_sport);
+ local_bh_disable();
+ __inet_inherit_port(parent, dq->sk);
+ local_bh_enable();
+ } else {
+ inet_sk(dq->sk)->inet_num = 0;
+ inet_hash_connect(&tcp_death_row, dq->sk);
+ inet_sk(dq->sk)->inet_num = ntohs(inet_sk(dq->sk)->inet_sport);
+ }
+
+ return 0;
+}
+
+static int sock_defer_hash(struct ckpt_ctx *ctx, struct sock *sock)
+{
+ struct dq_sock dq;
+
+ dq.sk = sock;
+ dq.ctx = ctx;
+
+ return deferqueue_add(ctx->deferqueue, &dq, sizeof(dq),
+ sock_hash_parent, NULL);
+}
+
+static int sock_inet_tcp_cptrst(struct ckpt_ctx *ctx,
+ struct tcp_sock *sk,
+ struct ckpt_hdr_socket_inet *hh,
+ int op)
+{
+ CKPT_COPY(op, hh->tcp.rcv_nxt, sk->rcv_nxt);
+ CKPT_COPY(op, hh->tcp.copied_seq, sk->copied_seq);
+ CKPT_COPY(op, hh->tcp.rcv_wup, sk->rcv_wup);
+ CKPT_COPY(op, hh->tcp.snd_nxt, sk->snd_nxt);
+ CKPT_COPY(op, hh->tcp.snd_una, sk->snd_una);
+ CKPT_COPY(op, hh->tcp.snd_sml, sk->snd_sml);
+ CKPT_COPY(op, hh->tcp.rcv_tstamp, sk->rcv_tstamp);
+ CKPT_COPY(op, hh->tcp.lsndtime, sk->lsndtime);
+
+ CKPT_COPY(op, hh->tcp.snd_wl1, sk->snd_wl1);
+ CKPT_COPY(op, hh->tcp.snd_wnd, sk->snd_wnd);
+ CKPT_COPY(op, hh->tcp.max_window, sk->max_window);
+ CKPT_COPY(op, hh->tcp.mss_cache, sk->mss_cache);
+ CKPT_COPY(op, hh->tcp.window_clamp, sk->window_clamp);
+ CKPT_COPY(op, hh->tcp.rcv_ssthresh, sk->rcv_ssthresh);
+ CKPT_COPY(op, hh->tcp.frto_highmark, sk->frto_highmark);
+ CKPT_COPY(op, hh->tcp.advmss, sk->advmss);
+ CKPT_COPY(op, hh->tcp.frto_counter, sk->frto_counter);
+ CKPT_COPY(op, hh->tcp.nonagle, sk->nonagle);
+
+ CKPT_COPY(op, hh->tcp.srtt, sk->srtt);
+ CKPT_COPY(op, hh->tcp.mdev, sk->mdev);
+ CKPT_COPY(op, hh->tcp.mdev_max, sk->mdev_max);
+ CKPT_COPY(op, hh->tcp.rttvar, sk->rttvar);
+ CKPT_COPY(op, hh->tcp.rtt_seq, sk->rtt_seq);
+
+ CKPT_COPY(op, hh->tcp.packets_out, sk->packets_out);
+ CKPT_COPY(op, hh->tcp.retrans_out, sk->retrans_out);
+
+ CKPT_COPY(op, hh->tcp.urg_data, sk->urg_data);
+ CKPT_COPY(op, hh->tcp.ecn_flags, sk->ecn_flags);
+ CKPT_COPY(op, hh->tcp.reordering, sk->reordering);
+ CKPT_COPY(op, hh->tcp.snd_up, sk->snd_up);
+
+ CKPT_COPY(op, hh->tcp.keepalive_probes, sk->keepalive_probes);
+
+ CKPT_COPY(op, hh->tcp.rcv_wnd, sk->rcv_wnd);
+ CKPT_COPY(op, hh->tcp.write_seq, sk->write_seq);
+ CKPT_COPY(op, hh->tcp.pushed_seq, sk->pushed_seq);
+ CKPT_COPY(op, hh->tcp.lost_out, sk->lost_out);
+ CKPT_COPY(op, hh->tcp.sacked_out, sk->sacked_out);
+ CKPT_COPY(op, hh->tcp.fackets_out, sk->fackets_out);
+ CKPT_COPY(op, hh->tcp.tso_deferred, sk->tso_deferred);
+ CKPT_COPY(op, hh->tcp.bytes_acked, sk->bytes_acked);
+
+ CKPT_COPY(op, hh->tcp.lost_cnt_hint, sk->lost_cnt_hint);
+ CKPT_COPY(op, hh->tcp.retransmit_high, sk->retransmit_high);
+
+ CKPT_COPY(op, hh->tcp.lost_retrans_low, sk->lost_retrans_low);
+
+ CKPT_COPY(op, hh->tcp.prior_ssthresh, sk->prior_ssthresh);
+ CKPT_COPY(op, hh->tcp.high_seq, sk->high_seq);
+
+ CKPT_COPY(op, hh->tcp.retrans_stamp, sk->retrans_stamp);
+ CKPT_COPY(op, hh->tcp.undo_marker, sk->undo_marker);
+ CKPT_COPY(op, hh->tcp.undo_retrans, sk->undo_retrans);
+ CKPT_COPY(op, hh->tcp.total_retrans, sk->total_retrans);
+
+ CKPT_COPY(op, hh->tcp.urg_seq, sk->urg_seq);
+ CKPT_COPY(op, hh->tcp.keepalive_time, sk->keepalive_time);
+ CKPT_COPY(op, hh->tcp.keepalive_intvl, sk->keepalive_intvl);
+
+ if (!skb_queue_empty(&sk->ucopy.prequeue))
+ printk(KERN_ERR "PREQUEUE!\n");
+
+ return 0;
+}
+
+static int sock_inet_restore_connection(struct sock *sk,
+ struct ckpt_hdr_socket_inet *hh)
+{
+ struct inet_sock *inet = inet_sk(sk);
+ int tcp_gso = sk->sk_family == AF_INET ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
+
+ inet->inet_daddr = hh->raddr.sin_addr.s_addr;
+ inet->inet_saddr = hh->laddr.sin_addr.s_addr;
+ inet->inet_rcv_saddr = inet->inet_saddr;
+
+ inet->inet_dport = hh->raddr.sin_port;
+ inet->inet_sport = hh->laddr.sin_port;
+
+ if (sk->sk_protocol == IPPROTO_TCP)
+ sk->sk_gso_type = tcp_gso;
+ else if (sk->sk_protocol == IPPROTO_UDP)
+ sk->sk_gso_type = SKB_GSO_UDP;
+ else {
+ ckpt_debug("Unsupported socket type while setting GSO\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sock_inet_cptrst(struct ckpt_ctx *ctx,
+ struct sock *sk,
+ struct ckpt_hdr_socket_inet *hh,
+ int op)
+{
+ struct inet_sock *inet = inet_sk(sk);
+ struct inet_connection_sock *icsk = inet_csk(sk);
+ int ret;
+
+ if (op == CKPT_CPT) {
+ CKPT_COPY(op, hh->daddr, inet->inet_daddr);
+ CKPT_COPY(op, hh->rcv_saddr, inet->inet_rcv_saddr);
+ CKPT_COPY(op, hh->dport, inet->inet_dport);
+ CKPT_COPY(op, hh->saddr, inet->inet_saddr);
+ CKPT_COPY(op, hh->sport, inet->inet_sport);
+ } else {
+ ret = sock_inet_restore_connection(sk, hh);
+ if (ret)
+ return ret;
+ }
+
+ CKPT_COPY(op, hh->num, inet->inet_num);
+ CKPT_COPY(op, hh->uc_ttl, inet->uc_ttl);
+ CKPT_COPY(op, hh->cmsg_flags, inet->cmsg_flags);
+
+ CKPT_COPY(op, hh->icsk_ack.pending, icsk->icsk_ack.pending);
+ CKPT_COPY(op, hh->icsk_ack.quick, icsk->icsk_ack.quick);
+ CKPT_COPY(op, hh->icsk_ack.pingpong, icsk->icsk_ack.pingpong);
+ CKPT_COPY(op, hh->icsk_ack.blocked, icsk->icsk_ack.blocked);
+ CKPT_COPY(op, hh->icsk_ack.ato, icsk->icsk_ack.ato);
+ CKPT_COPY(op, hh->icsk_ack.timeout, icsk->icsk_ack.timeout);
+ CKPT_COPY(op, hh->icsk_ack.lrcvtime, icsk->icsk_ack.lrcvtime);
+ CKPT_COPY(op,
+ hh->icsk_ack.last_seg_size, icsk->icsk_ack.last_seg_size);
+ CKPT_COPY(op, hh->icsk_ack.rcv_mss, icsk->icsk_ack.rcv_mss);
+
+ if (sk->sk_protocol == IPPROTO_TCP)
+ ret = sock_inet_tcp_cptrst(ctx, tcp_sk(sk), hh, op);
+ else if (sk->sk_protocol == IPPROTO_UDP)
+ ret = 0;
+ else {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "unknown socket protocol %d",
+ sk->sk_protocol);
+ }
+
+ if (sk->sk_family == AF_INET6) {
+ struct ipv6_pinfo *inet6 = inet6_sk(sk);
+ if (op == CKPT_CPT) {
+ ipv6_addr_copy(&hh->inet6.saddr, &inet6->saddr);
+ ipv6_addr_copy(&hh->inet6.rcv_saddr, &inet6->rcv_saddr);
+ ipv6_addr_copy(&hh->inet6.daddr, &inet6->daddr);
+ } else {
+ ipv6_addr_copy(&inet6->saddr, &hh->inet6.saddr);
+ ipv6_addr_copy(&inet6->rcv_saddr, &hh->inet6.rcv_saddr);
+ ipv6_addr_copy(&inet6->daddr, &hh->inet6.daddr);
+ }
+ }
+
+ return ret;
+}
+
int inet_checkpoint(struct ckpt_ctx *ctx, struct socket *sock)
{
struct ckpt_hdr_socket_inet *in;
@@ -43,6 +286,10 @@ int inet_checkpoint(struct ckpt_ctx *ctx, struct socket *sock)
if (ret)
goto out;
+ ret = sock_inet_cptrst(ctx, sock->sk, in, CKPT_CPT);
+ if (ret < 0)
+ goto out;
+
ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) in);
out:
ckpt_hdr_put(ctx, in);
@@ -55,11 +302,13 @@ int inet_collect(struct ckpt_ctx *ctx, struct socket *sock)
return ckpt_obj_collect(ctx, sock->sk, CKPT_OBJ_SOCK);
}
-static int inet_read_buffer(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
+static int inet_read_buffer(struct ckpt_ctx *ctx,
+ struct sk_buff_head *queue,
+ struct sock *sk)
{
struct sk_buff *skb = NULL;
- skb = sock_restore_skb(ctx);
+ skb = sock_restore_skb(ctx, sk);
if (IS_ERR(skb))
return PTR_ERR(skb);
@@ -67,7 +316,9 @@ static int inet_read_buffer(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
return skb->len;
}
-static int inet_read_buffers(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
+static int inet_read_buffers(struct ckpt_ctx *ctx,
+ struct sk_buff_head *queue,
+ struct sock *sk)
{
struct ckpt_hdr_socket_queue *h;
int ret = 0;
@@ -78,7 +329,7 @@ static int inet_read_buffers(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
return PTR_ERR(h);
for (i = 0; i < h->skb_count; i++) {
- ret = inet_read_buffer(ctx, queue);
+ ret = inet_read_buffer(ctx, queue, sk);
ckpt_debug("read inet buffer %i: %i", i, ret);
if (ret < 0)
goto out;
@@ -106,12 +357,12 @@ static int inet_deferred_restore_buffers(void *data)
struct sock *sk = dq->sk;
int ret;
- ret = inet_read_buffers(ctx, &sk->sk_receive_queue);
+ ret = inet_read_buffers(ctx, &sk->sk_receive_queue, sk);
ckpt_debug("(R) inet_read_buffers: %i\n", ret);
if (ret < 0)
return ret;
- ret = inet_read_buffers(ctx, &sk->sk_write_queue);
+ ret = inet_read_buffers(ctx, &sk->sk_write_queue, sk);
ckpt_debug("(W) inet_read_buffers: %i\n", ret);
return ret;
@@ -130,6 +381,19 @@ static int inet_defer_restore_buffers(struct ckpt_ctx *ctx, struct sock *sk)
static int inet_precheck(struct socket *sock, struct ckpt_hdr_socket_inet *in)
{
+ __u8 icsk_ack_mask = ICSK_ACK_SCHED | ICSK_ACK_TIMER |
+ ICSK_ACK_PUSHED | ICSK_ACK_PUSHED2;
+ __u16 urg_mask = TCP_URG_VALID | TCP_URG_NOTYET | TCP_URG_READ;
+ __u8 nonagle_mask = TCP_NAGLE_OFF | TCP_NAGLE_CORK | TCP_NAGLE_PUSH;
+ __u8 ecn_mask = TCP_ECN_OK | TCP_ECN_QUEUE_CWR | TCP_ECN_DEMAND_CWR;
+
+ if ((htons(in->laddr.sin_port) < PROT_SOCK) &&
+ !capable(CAP_NET_BIND_SERVICE)) {
+ ckpt_debug("unable to bind to port %hu\n",
+ htons(in->laddr.sin_port));
+ return -EINVAL;
+ }
+
if (in->laddr_len > sizeof(struct sockaddr_in)) {
ckpt_debug("laddr_len is too big\n");
return -EINVAL;
@@ -140,6 +404,75 @@ static int inet_precheck(struct socket *sock, struct ckpt_hdr_socket_inet *in)
return -EINVAL;
}
+ /* Set ato to the default */
+ in->icsk_ack.ato = TCP_ATO_MIN;
+
+ /* No quick acks are scheduled after a restart */
+ in->icsk_ack.quick = 0;
+
+ if (in->icsk_ack.pending & ~icsk_ack_mask) {
+ ckpt_debug("invalid pending flags 0x%x\n",
+ in->icsk_ack.pending & ~icsk_ack_mask);
+ return -EINVAL;
+ }
+
+ if (in->icsk_ack.pingpong > 1) {
+ ckpt_debug("invalid icsk_ack.pingpong value\n");
+ return -EINVAL;
+ }
+
+ if (in->icsk_ack.blocked > 1) {
+ ckpt_debug("invalid icsk_ack.blocked value\n");
+ return -EINVAL;
+ }
+
+ /* do_tcp_setsockopt() quietly makes this coercion */
+ if (in->tcp.window_clamp < (SOCK_MIN_RCVBUF / 2))
+ in->tcp.window_clamp = SOCK_MIN_RCVBUF / 2;
+ else
+ in->tcp.window_clamp = min(in->tcp.window_clamp, 65535U);
+
+ if (in->tcp.rcv_ssthresh > (4U * in->tcp.advmss))
+ in->tcp.rcv_ssthresh = 4U * in->tcp.advmss;
+
+ /* These will all be recalculated on the next call to
+ * tcp_rtt_estimator()
+ */
+ in->tcp.srtt = in->tcp.mdev = in->tcp.mdev_max = 0;
+ in->tcp.rttvar = in->tcp.rtt_seq = 0;
+
+ /* Might want to set packets_out to zero ? */
+
+ if (in->tcp.rcv_wnd > MAX_TCP_WINDOW)
+ in->tcp.rcv_wnd = MAX_TCP_WINDOW;
+
+ if (in->tcp.keepalive_intvl > MAX_TCP_KEEPINTVL) {
+ ckpt_debug("keepalive_intvl %i out of range\n",
+ in->tcp.keepalive_intvl);
+ return -EINVAL;
+ }
+
+ if (in->tcp.keepalive_probes > MAX_TCP_KEEPCNT) {
+ ckpt_debug("Invalid keepalive_probes value %i\n",
+ in->tcp.keepalive_probes);
+ return -EINVAL;
+ }
+
+ if (in->tcp.urg_data & ~urg_mask) {
+ ckpt_debug("Invalid urg_data value\n");
+ return -EINVAL;
+ }
+
+ if (in->tcp.nonagle & ~nonagle_mask) {
+ ckpt_debug("Invalid nonagle value\n");
+ return -EINVAL;
+ }
+
+ if (in->tcp.ecn_flags & ~ecn_mask) {
+ ckpt_debug("Invalid ecn_flags value\n");
+ return -EINVAL;
+ }
+
return 0;
}
@@ -177,8 +510,35 @@ int inet_restore(struct ckpt_ctx *ctx,
ckpt_debug("inet listen: %i\n", ret);
if (ret < 0)
goto out;
+
+ /* We are a listening socket, so add ourselves
+ * to the list of parent sockets. This will
+ * allow our children to find us later and
+ * link up
+ */
+
+ ret = sock_listening_list_add(ctx, sock->sk);
+ if (ret < 0)
+ goto out;
}
} else {
+ ret = sock_inet_cptrst(ctx, sock->sk, in, CKPT_RST);
+ if (ret)
+ goto out;
+
+ if ((h->sock.state == TCP_ESTABLISHED) &&
+ (h->sock.protocol == IPPROTO_TCP)) {
+ /* A connected socket that was spawned from an
+ * accept() needs to be hashed with its parent
+ * listening socket in order to receive
+ * traffic on the original port. Since we may
+ * not have restarted the parent yet, we defer
+ * this until later when we know we have all
+ * the listening sockets accounted for.
+ */
+ ret = sock_defer_hash(ctx, sock->sk);
+ }
+
if (!sock_flag(sock->sk, SOCK_DEAD))
ret = inet_defer_restore_buffers(ctx, sock->sk);
}
@@ -187,4 +547,3 @@ int inet_restore(struct ckpt_ctx *ctx,
return ret;
}
-
--
1.6.3.3
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply related
* Re: [PATCH 1/3] netlink: fix NETLINK_RECV_NO_ENOBUFS in netlink_set_err()
From: Pablo Neira Ayuso @ 2010-03-17 16:17 UTC (permalink / raw)
To: Patrick McHardy; +Cc: netdev, davem
In-Reply-To: <4BA0F4C0.8050901@trash.net>
Patrick McHardy wrote:
> Pablo Neira Ayuso wrote:
>> Currently, ENOBUFS errors are reported to the socket via
>> netlink_set_err() even if NETLINK_RECV_NO_ENOBUFS is set. However,
>> that should not happen. This fixes this problem and it changes the
>> prototype of netlink_set_err() to return the number of sockets whose
>> error has been set. This allows to know if any error has been set.
>> This return value is used in the next patch in these bugfix series.
>
> But that only happens if we have a message allocate error, which is
> a different situation than rcvqueue overrun, which I thought the
> original patch was supposed to handle (disable netlink congestion
> control).
Yes, allocation is a different situation but we still report ENOBUFS to
user-space. I think that NETLINK_RECV_NO_ENOBUFS is there to a) disable
ENOBUFS reports to user-space and b) disable Netlink congestion.
> Is there any problem with these errors?
Specifically in ctnetlink, if we fail to allocate a message in ctnetlink
and NETLINK_RECV_NO_ENOBUFS is set, we still lose an event and that
should not happen.
^ permalink raw reply
* Re: [PATCH] xfrm: cache bundle lookup results in flow cache
From: Timo Teräs @ 2010-03-17 16:32 UTC (permalink / raw)
To: Herbert Xu; +Cc: netdev
In-Reply-To: <4BA0FBB6.10208@iki.fi>
Timo Teräs wrote:
>>> Also. With this and your recent flowi patch, I'm seeing pmtu
>>> issues. Seems like xfrm_bundle_ok uses the original dst which
>>> resulted in the creation of the bundle. Somehow that dst
>>> does not get updated with pmtu... but the new dst used in
>>> next xfrm_lookup for same target does have proper mtu.
>>> I'm debugging right now why this is happening. Any ideas?
>>
>> The dynamic MTU is always maintained in a normal dst object in
>> the IPv4 routing cache. Each xfrm_dst points to such a dst
>> through xdst->route.
>>
>> If you were looking at the xfrm_dst's own MTU then that may well
>> cause problems.
>
> I figured the root cause. The original dst gets expired
> rt_genid goes old. But xfrm_dst does not notice that so it
> won't create a new bundle. xfrm_bundle_ok calls dst_check,
> but dst->obsolete = 0, and ipv4_dst_check is a no-op anyway.
>
> Somehow the rtable object should be able to tell back to
> xfrm that the dst is not good anymore. Any ideas?
Checked ipv6, it does like xfrm: sets obsolote to -1 and
on dst_check checks the genid. We need to do same in for
ipv4. I just wrote an hack, and tested it. It solves the
pmtu issues.
I will post a proper patch soon.
- Timo
^ permalink raw reply
* [PATCH] gigaset: fix build failure
From: Tilman Schmidt @ 2010-03-17 17:37 UTC (permalink / raw)
To: Karsten Keil, David Miller
Cc: Randy Dunlap, Stephen Rothwell, linux-next, Hansjoerg Lipp,
isdn4linux, i4ldeveloper, netdev, linux-kernel, stable
Update the dummy LL interface to the LL interface change
introduced by commit daab433c03c15fd642c71c94eb51bdd3f32602c8.
This fixes the build failure occurring after that commit when
enabling ISDN_DRV_GIGASET but neither ISDN_I4L nor ISDN_CAPI.
Impact: bugfix
Signed-off-by: Tilman Schmidt <tilman@imap.cc>
---
drivers/isdn/gigaset/dummyll.c | 14 +++++++++++---
1 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/drivers/isdn/gigaset/dummyll.c b/drivers/isdn/gigaset/dummyll.c
index 5b27c99..bd0b1ea 100644
--- a/drivers/isdn/gigaset/dummyll.c
+++ b/drivers/isdn/gigaset/dummyll.c
@@ -57,12 +57,20 @@ void gigaset_isdn_stop(struct cardstate *cs)
{
}
-int gigaset_isdn_register(struct cardstate *cs, const char *isdnid)
+int gigaset_isdn_regdev(struct cardstate *cs, const char *isdnid)
{
- pr_info("no ISDN subsystem interface\n");
return 1;
}
-void gigaset_isdn_unregister(struct cardstate *cs)
+void gigaset_isdn_unregdev(struct cardstate *cs)
+{
+}
+
+void gigaset_isdn_regdrv(void)
+{
+ pr_info("no ISDN subsystem interface\n");
+}
+
+void gigaset_isdn_unregdrv(void)
{
}
--
1.6.5.3.298.g39add
^ permalink raw reply related
* Re: [PATCH] vhost: fix error handling in vring ioctls
From: Laurent Chavey @ 2010-03-17 17:54 UTC (permalink / raw)
To: Michael S. Tsirkin; +Cc: Jiri Slaby, kvm, virtualization, netdev, linux-kernel
In-Reply-To: <20100317144249.GA3984@redhat.com>
Acked-by: chavey@google.com
On Wed, Mar 17, 2010 at 7:42 AM, Michael S. Tsirkin <mst@redhat.com> wrote:
> Stanse found a locking problem in vhost_set_vring:
> several returns from VHOST_SET_VRING_KICK, VHOST_SET_VRING_CALL,
> VHOST_SET_VRING_ERR with the vq->mutex held.
> Fix these up.
>
> Reported-by: Jiri Slaby <jirislaby@gmail.com>
> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
> ---
> drivers/vhost/vhost.c | 18 ++++++++++++------
> 1 files changed, 12 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c
> index 7cd55e0..7bd7a1e 100644
> --- a/drivers/vhost/vhost.c
> +++ b/drivers/vhost/vhost.c
> @@ -476,8 +476,10 @@ static long vhost_set_vring(struct vhost_dev *d, int ioctl, void __user *argp)
> if (r < 0)
> break;
> eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd);
> - if (IS_ERR(eventfp))
> - return PTR_ERR(eventfp);
> + if (IS_ERR(eventfp)) {
> + r = PTR_ERR(eventfp);
> + break;
> + }
> if (eventfp != vq->kick) {
> pollstop = filep = vq->kick;
> pollstart = vq->kick = eventfp;
> @@ -489,8 +491,10 @@ static long vhost_set_vring(struct vhost_dev *d, int ioctl, void __user *argp)
> if (r < 0)
> break;
> eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd);
> - if (IS_ERR(eventfp))
> - return PTR_ERR(eventfp);
> + if (IS_ERR(eventfp)) {
> + r = PTR_ERR(eventfp);
> + break;
> + }
> if (eventfp != vq->call) {
> filep = vq->call;
> ctx = vq->call_ctx;
> @@ -505,8 +509,10 @@ static long vhost_set_vring(struct vhost_dev *d, int ioctl, void __user *argp)
> if (r < 0)
> break;
> eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd);
> - if (IS_ERR(eventfp))
> - return PTR_ERR(eventfp);
> + if (IS_ERR(eventfp)) {
> + r = PTR_ERR(eventfp);
> + break;
> + }
> if (eventfp != vq->error) {
> filep = vq->error;
> vq->error = eventfp;
> --
> 1.7.0.18.g0d53a5
> --
> To unsubscribe from this list: send the line "unsubscribe netdev" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
* RE: e1000e: Fix build with CONFIG_PM disabled.
From: Tantilov, Emil S @ 2010-03-17 17:57 UTC (permalink / raw)
To: David Miller, rjw@sisk.pl
Cc: netdev@vger.kernel.org, Allan, Bruce W, Brandeburg, Jesse,
Kirsher, Jeffrey T
In-Reply-To: <20100316.234100.133957798.davem@davemloft.net>
David Miller wrote:
> I had to add the following patch to fix the build after
> your changes.
>
> Thanks.
>
> e1000e: Fix build with CONFIG_PM disabled.
>
> Signed-off-by: David S. Miller <davem@davemloft.net>
> ---
> drivers/net/e1000e/netdev.c | 2 ++
> 1 files changed, 2 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/net/e1000e/netdev.c b/drivers/net/e1000e/netdev.c
> index 79b33c5..b96532a 100644
> --- a/drivers/net/e1000e/netdev.c
> +++ b/drivers/net/e1000e/netdev.c
> @@ -5475,6 +5475,7 @@ static DEFINE_PCI_DEVICE_TABLE(e1000_pci_tbl) =
> { };
> MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);
>
> +#ifdef CONFIG_PM
> static const struct dev_pm_ops e1000_pm_ops = {
> .suspend = e1000_suspend,
> .resume = e1000_resume,
> @@ -5486,6 +5487,7 @@ static const struct dev_pm_ops e1000_pm_ops = {
> .runtime_resume = e1000_runtime_resume,
> .runtime_idle = e1000_idle,
> };
> +#endif
>
> /* PCI Device API Driver */
> static struct pci_driver e1000_driver = {
> --
> 1.6.6.1
But then the driver build fails again when CONFIG_PM_RUNTIME is not set.
drivers/net/e1000e/netdev.c: In function `e1000_runtime_resume`:
drivers/net/e1000e/netdev.c:4807: error: `struct dev_pm_info` has no member named `runtime_auto`
Thanks,
Emil
^ permalink raw reply
* Re: [PATCH 1/1] 3c59x: Acquire vortex lock instead of disabling irq
From: David Miller @ 2010-03-17 18:03 UTC (permalink / raw)
To: chase.douglas; +Cc: netdev
In-Reply-To: <1268836396-23943-2-git-send-email-chase.douglas@canonical.com>
From: Chase Douglas <chase.douglas@canonical.com>
Date: Wed, 17 Mar 2010 10:33:16 -0400
> Last year, threaded IRQ handlers were introduced to the mainline kernel.
> This change requires the disable_irq function to sleep if any IRQ
> handler threads for a given IRQ line are running.
>
> Back in 2006, while working on the -rt patch set that had threaded IRQ
> handlers, the vortex_timer function was causing scheduling bugs because
> it is run in softirq context and called disable_irq. This patch was the
> best fix determined at the time, and still exists in the .33 -rt
> patchset. Now that threaded IRQ handlers are present in the mainline
> kernel we need to apply the patch there as well.
>
> http://lkml.org/lkml/2006/5/12/178
>
> BugLink: http://bugs.launchpad.net/bugs/533335
>
> Signed-off-by: Chase Douglas <chase.douglas@canonical.com>
This code is very much intentionally using disable_irq*().
The operation being performed here is extremely expensive,
and during that time if we have cpu interrupts disabled
serial devices will drop characters etc.
^ permalink raw reply
* Re: e1000e: Fix build with CONFIG_PM disabled.
From: David Miller @ 2010-03-17 18:17 UTC (permalink / raw)
To: emil.s.tantilov
Cc: rjw, netdev, bruce.w.allan, jesse.brandeburg, jeffrey.t.kirsher
In-Reply-To: <EA929A9653AAE14F841771FB1DE5A1365FE4909419@rrsmsx501.amr.corp.intel.com>
From: "Tantilov, Emil S" <emil.s.tantilov@intel.com>
Date: Wed, 17 Mar 2010 11:57:59 -0600
> But then the driver build fails again when CONFIG_PM_RUNTIME is not set.
>
> drivers/net/e1000e/netdev.c: In function `e1000_runtime_resume`:
> drivers/net/e1000e/netdev.c:4807: error: `struct dev_pm_info` has no member named `runtime_auto`
Ugh, can someone send me a fix for that?
^ permalink raw reply
* Re: [PATCH 1/1] 3c59x: Acquire vortex lock instead of disabling irq
From: Chase Douglas @ 2010-03-17 18:20 UTC (permalink / raw)
To: David Miller; +Cc: netdev
In-Reply-To: <20100317.110320.15445884.davem@davemloft.net>
On Wed, Mar 17, 2010 at 2:03 PM, David Miller <davem@davemloft.net> wrote:
> From: Chase Douglas <chase.douglas@canonical.com>
> Date: Wed, 17 Mar 2010 10:33:16 -0400
>
>> Last year, threaded IRQ handlers were introduced to the mainline kernel.
>> This change requires the disable_irq function to sleep if any IRQ
>> handler threads for a given IRQ line are running.
>>
>> Back in 2006, while working on the -rt patch set that had threaded IRQ
>> handlers, the vortex_timer function was causing scheduling bugs because
>> it is run in softirq context and called disable_irq. This patch was the
>> best fix determined at the time, and still exists in the .33 -rt
>> patchset. Now that threaded IRQ handlers are present in the mainline
>> kernel we need to apply the patch there as well.
>>
>> http://lkml.org/lkml/2006/5/12/178
>>
>> BugLink: http://bugs.launchpad.net/bugs/533335
>>
>> Signed-off-by: Chase Douglas <chase.douglas@canonical.com>
>
> This code is very much intentionally using disable_irq*().
>
> The operation being performed here is extremely expensive,
> and during that time if we have cpu interrupts disabled
> serial devices will drop characters etc.
If that's the case, what's the solution? It's not safe to call
disable_irq* in softirq context anymore. With the patch we have a
stable driver that may cause some serial devices to drop characters.
Without the patch we have an unstable driver that can lock up. To me
it seems the latter is preferable to the former, especially when the
lockup is occurs somewhat frequently.
-- Chase
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox