af_iucv: handle netdev events
authorUrsula Braun <ursula.braun@de.ibm.com>
Wed, 7 Mar 2012 02:06:23 +0000 (02:06 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 8 Mar 2012 06:52:24 +0000 (22:52 -0800)
In case of transport through HiperSockets the underlying network
interface may switch to DOWN state or the underlying network device
may recover. In both cases the socket must change to IUCV_DISCONN
state. If the interface goes down, af_iucv has a chance to notify
its connection peer in addition.

Signed-off-by: Ursula Braun <ursula.braun@de.ibm.com>
Signed-off-by: Frank Blaschka <frank.blaschka@de.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/iucv/af_iucv.c

index 98d1f0b..31537c5 100644 (file)
@@ -453,14 +453,28 @@ static void iucv_sever_path(struct sock *sk, int with_user_data)
        }
 }
 
+/* Send FIN through an IUCV socket for HIPER transport */
+static int iucv_send_ctrl(struct sock *sk, u8 flags)
+{
+       int err = 0;
+       int blen;
+       struct sk_buff *skb;
+
+       blen = sizeof(struct af_iucv_trans_hdr) + ETH_HLEN;
+       skb = sock_alloc_send_skb(sk, blen, 1, &err);
+       if (skb) {
+               skb_reserve(skb, blen);
+               err = afiucv_hs_send(NULL, sk, skb, flags);
+       }
+       return err;
+}
+
 /* Close an IUCV socket */
 static void iucv_sock_close(struct sock *sk)
 {
        struct iucv_sock *iucv = iucv_sk(sk);
        unsigned long timeo;
        int err = 0;
-       int blen;
-       struct sk_buff *skb;
 
        lock_sock(sk);
 
@@ -471,14 +485,7 @@ static void iucv_sock_close(struct sock *sk)
 
        case IUCV_CONNECTED:
                if (iucv->transport == AF_IUCV_TRANS_HIPER) {
-                       /* send fin */
-                       blen = sizeof(struct af_iucv_trans_hdr) + ETH_HLEN;
-                       skb = sock_alloc_send_skb(sk, blen, 1, &err);
-                       if (skb) {
-                               skb_reserve(skb, blen);
-                               err = afiucv_hs_send(NULL, sk, skb,
-                                                    AF_IUCV_FLAG_FIN);
-                       }
+                       err = iucv_send_ctrl(sk, AF_IUCV_FLAG_FIN);
                        sk->sk_state = IUCV_DISCONN;
                        sk->sk_state_change(sk);
                }
@@ -782,26 +789,6 @@ static int iucv_sock_autobind(struct sock *sk)
        return err;
 }
 
-static int afiucv_hs_connect(struct socket *sock)
-{
-       struct sock *sk = sock->sk;
-       struct sk_buff *skb;
-       int blen = sizeof(struct af_iucv_trans_hdr) + ETH_HLEN;
-       int err = 0;
-
-       /* send syn */
-       skb = sock_alloc_send_skb(sk, blen, 1, &err);
-       if (!skb) {
-               err = -ENOMEM;
-               goto done;
-       }
-       skb->dev = NULL;
-       skb_reserve(skb, blen);
-       err = afiucv_hs_send(NULL, sk, skb, AF_IUCV_FLAG_SYN);
-done:
-       return err;
-}
-
 static int afiucv_path_connect(struct socket *sock, struct sockaddr *addr)
 {
        struct sockaddr_iucv *sa = (struct sockaddr_iucv *) addr;
@@ -882,7 +869,7 @@ static int iucv_sock_connect(struct socket *sock, struct sockaddr *addr,
        memcpy(iucv->dst_name, sa->siucv_name, 8);
 
        if (iucv->transport == AF_IUCV_TRANS_HIPER)
-               err = afiucv_hs_connect(sock);
+               err = iucv_send_ctrl(sock->sk, AF_IUCV_FLAG_SYN);
        else
                err = afiucv_path_connect(sock, addr);
        if (err)
@@ -1332,8 +1319,7 @@ static int iucv_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
        struct sock *sk = sock->sk;
        struct iucv_sock *iucv = iucv_sk(sk);
        unsigned int copied, rlen;
-       struct sk_buff *skb, *rskb, *cskb, *sskb;
-       int blen;
+       struct sk_buff *skb, *rskb, *cskb;
        int err = 0;
 
        if ((sk->sk_state == IUCV_DISCONN) &&
@@ -1422,15 +1408,7 @@ static int iucv_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
                                iucv_process_message_q(sk);
                        if (atomic_read(&iucv->msg_recv) >=
                                                        iucv->msglimit / 2) {
-                               /* send WIN to peer */
-                               blen = sizeof(struct af_iucv_trans_hdr) +
-                                       ETH_HLEN;
-                               sskb = sock_alloc_send_skb(sk, blen, 1, &err);
-                               if (sskb) {
-                                       skb_reserve(sskb, blen);
-                                       err = afiucv_hs_send(NULL, sk, sskb,
-                                                            AF_IUCV_FLAG_WIN);
-                               }
+                               err = iucv_send_ctrl(sk, AF_IUCV_FLAG_WIN);
                                if (err) {
                                        sk->sk_state = IUCV_DISCONN;
                                        sk->sk_state_change(sk);
@@ -2289,6 +2267,44 @@ out_unlock:
        }
 
 }
+
+/*
+ * afiucv_netdev_event: handle netdev notifier chain events
+ */
+static int afiucv_netdev_event(struct notifier_block *this,
+                              unsigned long event, void *ptr)
+{
+       struct net_device *event_dev = (struct net_device *)ptr;
+       struct hlist_node *node;
+       struct sock *sk;
+       struct iucv_sock *iucv;
+
+       switch (event) {
+       case NETDEV_REBOOT:
+       case NETDEV_GOING_DOWN:
+               sk_for_each(sk, node, &iucv_sk_list.head) {
+                       iucv = iucv_sk(sk);
+                       if ((iucv->hs_dev == event_dev) &&
+                           (sk->sk_state == IUCV_CONNECTED)) {
+                               if (event == NETDEV_GOING_DOWN)
+                                       iucv_send_ctrl(sk, AF_IUCV_FLAG_FIN);
+                               sk->sk_state = IUCV_DISCONN;
+                               sk->sk_state_change(sk);
+                       }
+               }
+               break;
+       case NETDEV_DOWN:
+       case NETDEV_UNREGISTER:
+       default:
+               break;
+       }
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block afiucv_netdev_notifier = {
+       .notifier_call = afiucv_netdev_event,
+};
+
 static const struct proto_ops iucv_sock_ops = {
        .family         = PF_IUCV,
        .owner          = THIS_MODULE,
@@ -2388,7 +2404,8 @@ static int __init afiucv_init(void)
                err = afiucv_iucv_init();
                if (err)
                        goto out_sock;
-       }
+       } else
+               register_netdevice_notifier(&afiucv_netdev_notifier);
        dev_add_pack(&iucv_packet_type);
        return 0;
 
@@ -2409,7 +2426,8 @@ static void __exit afiucv_exit(void)
                driver_unregister(&af_iucv_driver);
                pr_iucv->iucv_unregister(&af_iucv_handler, 0);
                symbol_put(iucv_if);
-       }
+       } else
+               unregister_netdevice_notifier(&afiucv_netdev_notifier);
        dev_remove_pack(&iucv_packet_type);
        sock_unregister(PF_IUCV);
        proto_unregister(&iucv_proto);