Am Freitag 18 November 2005 04:03 schrieb jamal:
> > ok, I'll produce a patch then - first version will be available tomorrow
> > evening.
>
> beauty.
Ok, here is the first 'merger' patch. At this stage, it is only compilation
tested against 2.6.14.
This is the data flow:
-Device changes __LINK_STATE_(NOCARRIER|DORMANT) flag
-linkwatch is scheduled
-During linkwatch, the device is queried for it's operational state either
using a device specified method or a generic one
-Operational state is limited to IF_OPER_DORMANT if link mode tells so
-state is written to dev->operstate
-notification is sent out via netdev_state_change() as before
Userspace can intervene after setting link mode to IF_LINK_MODE_DORMANT
(implementation incomplete as I'm yet not familiar enough with writing values
through netlink, Thomas, can you help here?)
-Calls pseudo_set_operstate()
-operstate is either changed from DORMANT to UP or reverse
-notification is sent out via netdev_state_change()
linkwatch and pseudo* as serialized by rtnl and dev_base_lock.
To demonstrate a stacked device, I've implemented a private RFC2863 state
query for VLANs that goes to LOWERLAYERDOWN if the underlying device is DOWN.
With the device specific RFC2863 state query method, we could even limit
LINK_STATE_DORMANT to the devices that need it, but I have the impression
that this is generic enough to be part of struct net_device.
I'm currently emitting more netlink notifications than you've shown in your
state diagram. I'll comment on this later, still got other stuff to do for
today.
> -> UP->DORMANT; i am not sure if that transition is needed.
Anyway, this transition is needed. Switches can be configured to rerun 802.1X
regularly after initial authentication.
> I will attempt some ascii art shortly. Actually if you agree with it
> please include it in your patch so it is easier for people to make
> changes in the future.
Yes, I think I have to write a short text for the Documentation/ directory
anyway.
Stefan
diff -X dontdiff -urp linux-2.6.14/include/linux/if.h linux-2.6.14-rfc2863/include/linux/if.h
--- linux-2.6.14/include/linux/if.h 2005-11-02 11:07:32.000000000 +0100
+++ linux-2.6.14-rfc2863/include/linux/if.h 2005-11-18 20:14:25.000000000 +0100
@@ -33,7 +33,7 @@
#define IFF_LOOPBACK 0x8 /* is a loopback net */
#define IFF_POINTOPOINT 0x10 /* interface is has p-p link */
#define IFF_NOTRAILERS 0x20 /* avoid use of trailers */
-#define IFF_RUNNING 0x40 /* interface running and carrier ok */
+#define IFF_RUNNING 0x40 /* interface RFC2863 OPER_UP */
#define IFF_NOARP 0x80 /* no ARP protocol */
#define IFF_PROMISC 0x100 /* receive all packets */
#define IFF_ALLMULTI 0x200 /* receive all multicast packets*/
@@ -43,12 +43,16 @@
#define IFF_MULTICAST 0x1000 /* Supports multicast */
-#define IFF_VOLATILE (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_MASTER|IFF_SLAVE|IFF_RUNNING)
-
#define IFF_PORTSEL 0x2000 /* can set media type */
#define IFF_AUTOMEDIA 0x4000 /* auto media select active */
#define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses*/
+#define IFF_CARRIER 0x10000 /* driver signals carrier */
+#define IFF_DORMANT 0x20000 /* driver signals dormant */
+
+#define IFF_VOLATILE (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|\
+ IFF_MASTER|IFF_SLAVE|IFF_RUNNING|IFF_CARRIER|IFF_DORMANT)
+
/* Private (from user) interface flags (netdevice->priv_flags). */
#define IFF_802_1Q_VLAN 0x1 /* 802.1Q VLAN device. */
#define IFF_EBRIDGE 0x2 /* Ethernet bridging device. */
@@ -80,6 +84,22 @@
#define IF_PROTO_FR_ETH_PVC 0x200B
#define IF_PROTO_RAW 0x200C /* RAW Socket */
+/* RFC 2863 operational status */
+enum {
+ IF_OPER_UNKNOWN,
+ IF_OPER_NOTPRESENT,
+ IF_OPER_DOWN,
+ IF_OPER_LOWERLAYERDOWN,
+ IF_OPER_TESTING,
+ IF_OPER_DORMANT,
+ IF_OPER_UP,
+};
+
+/* link modes */
+enum {
+ IF_LINK_MODE_DEFAULT,
+ IF_LINK_MODE_DORMANT, /* limit upward transition to dormant */
+};
/*
* Device mapping structure. I'd just gone off and designed a
diff -X dontdiff -urp linux-2.6.14/include/linux/netdevice.h linux-2.6.14-rfc2863/include/linux/netdevice.h
--- linux-2.6.14/include/linux/netdevice.h 2005-11-02 11:08:10.000000000 +0100
+++ linux-2.6.14-rfc2863/include/linux/netdevice.h 2005-11-18 19:53:32.000000000 +0100
@@ -230,7 +230,8 @@ enum netdev_state_t
__LINK_STATE_SCHED,
__LINK_STATE_NOCARRIER,
__LINK_STATE_RX_SCHED,
- __LINK_STATE_LINKWATCH_PENDING
+ __LINK_STATE_LINKWATCH_PENDING,
+ __LINK_STATE_DORMANT,
};
@@ -334,11 +335,14 @@ struct net_device
*/
- unsigned short flags; /* interface flags (a la BSD) */
+ unsigned int flags; /* interface flags (a la BSD) */
unsigned short gflags;
unsigned short priv_flags; /* Like 'flags' but invisible to userspace. */
unsigned short padded; /* How much padding added by alloc_netdev() */
+ unsigned char operstate; /* RFC2863 operstate */
+ unsigned char link_mode; /* mapping policy to operstate */
+
unsigned mtu; /* interface MTU value */
unsigned short type; /* interface hardware type */
unsigned short hard_header_len; /* hardware hdr length */
@@ -491,6 +495,8 @@ struct net_device
void (*poll_controller)(struct net_device *dev);
#endif
+ unsigned char (*oper_state)(const struct net_device *dev, unsigned char genstate);
+
/* bridge stuff */
struct net_bridge_port *br_port;
@@ -712,6 +718,10 @@ static inline void dev_put(struct net_de
/* Carrier loss detection, dial on demand. The functions netif_carrier_on
* and _off may be called from IRQ context, but it is caller
* who is responsible for serialization of these calls.
+ *
+ * The name carrier is inappropriate, these functions should really be
+ * called netif_lowerlayer_*() because they represent the state of any
+ * kind of lower layer not just hardware media.
*/
extern void linkwatch_fire_event(struct net_device *dev);
@@ -727,6 +737,23 @@ extern void netif_carrier_on(struct net_
extern void netif_carrier_off(struct net_device *dev);
+static inline void netif_dormant_on(struct net_device *dev)
+{
+ if (!test_and_set_bit(__LINK_STATE_DORMANT, &dev->state))
+ linkwatch_fire_event(dev);
+}
+
+static inline void netif_dormant_off(struct net_device *dev)
+{
+ if (test_and_clear_bit(__LINK_STATE_DORMANT, &dev->state))
+ linkwatch_fire_event(dev);
+}
+
+static inline int netif_dormant(const struct net_device *dev)
+{
+ return test_bit(__LINK_STATE_DORMANT, &dev->state);
+}
+
/* Hot-plugging. */
static inline int netif_device_present(struct net_device *dev)
{
diff -X dontdiff -urp linux-2.6.14/include/linux/rtnetlink.h linux-2.6.14-rfc2863/include/linux/rtnetlink.h
--- linux-2.6.14/include/linux/rtnetlink.h 2005-11-02 11:08:11.000000000 +0100
+++ linux-2.6.14-rfc2863/include/linux/rtnetlink.h 2005-11-18 20:14:05.000000000 +0100
@@ -733,6 +733,8 @@ enum
#define IFLA_MAP IFLA_MAP
IFLA_WEIGHT,
#define IFLA_WEIGHT IFLA_WEIGHT
+ IFLA_OPERSTATE,
+ IFLA_LINKMODE,
__IFLA_MAX
};
diff -X dontdiff -urp linux-2.6.14/net/8021q/vlan.c linux-2.6.14-rfc2863/net/8021q/vlan.c
--- linux-2.6.14/net/8021q/vlan.c 2005-11-02 11:07:35.000000000 +0100
+++ linux-2.6.14-rfc2863/net/8021q/vlan.c 2005-11-18 20:54:12.000000000 +0100
@@ -68,7 +68,7 @@ static struct packet_type vlan_packet_ty
/* Bits of netdev state that are propagated from real device to virtual */
#define VLAN_LINK_STATE_MASK \
- ((1<<__LINK_STATE_PRESENT)|(1<<__LINK_STATE_NOCARRIER))
+ ((1<<__LINK_STATE_PRESENT)|(1<<__LINK_STATE_NOCARRIER)|(1<<__LINK_STATE_DORMANT))
/* End of global variables definitions. */
@@ -198,6 +198,16 @@ static void vlan_rcu_free(struct rcu_hea
}
+/* Return RFC2863 state of VLAN device. Dataflow:
+ * -state change in real device triggers vlan_device_event()
+ * -linkwatch_fire_event() schedules event if needed
+ * -we get called, use operstate of underlying device only
+ */
+static unsigned char vlan_oper_state(const struct net_device *dev, unsigned char genstate) {
+ const struct net_device *real_dev = VLAN_DEV_INFO(dev)->real_dev;
+ return real_dev->operstate==IF_OPER_DOWN?IF_OPER_LOWERLAYERDOWN:real_dev->operstate;
+}
+
/* This returns 0 if everything went fine.
* It will return 1 if the group was killed as a result.
* A negative return indicates failure.
@@ -341,6 +351,7 @@ static void vlan_setup(struct net_device
new_dev->set_multicast_list = vlan_dev_set_multicast_list;
new_dev->destructor = free_netdev;
new_dev->do_ioctl = vlan_dev_ioctl;
+ new_dev->oper_state = vlan_oper_state;
}
/* Attach a VLAN device to a mac address (ie Ethernet Card).
@@ -586,6 +597,11 @@ static int vlan_device_event(struct noti
netif_carrier_off(vlandev);
}
+ if (dev->operstate != vlandev->operstate &&
+ !(dev->operstate == IF_OPER_DOWN &&
+ vlandev->operstate == IF_OPER_LOWERLAYERDOWN))
+ linkwatch_fire_event(vlandev);
+
if ((vlandev->state & VLAN_LINK_STATE_MASK) != flgs) {
vlandev->state = (vlandev->state &~ VLAN_LINK_STATE_MASK)
| flgs;
diff -X dontdiff -urp linux-2.6.14/net/core/dev.c linux-2.6.14-rfc2863/net/core/dev.c
--- linux-2.6.14/net/core/dev.c 2005-11-06 17:35:22.000000000 +0100
+++ linux-2.6.14-rfc2863/net/core/dev.c 2005-11-18 21:14:00.000000000 +0100
@@ -2141,12 +2141,20 @@ unsigned dev_get_flags(const struct net_
flags = (dev->flags & ~(IFF_PROMISC |
IFF_ALLMULTI |
- IFF_RUNNING)) |
+ IFF_RUNNING |
+ IFF_CARRIER |
+ IFF_DORMANT)) |
(dev->gflags & (IFF_PROMISC |
IFF_ALLMULTI));
- if (netif_running(dev) && netif_carrier_ok(dev))
- flags |= IFF_RUNNING;
+ if (netif_running(dev)) {
+ if (dev->operstate == IF_OPER_UP)
+ flags |= IFF_RUNNING;
+ if (netif_carrier_ok(dev))
+ flags |= IFF_CARRIER;
+ if (netif_dormant(dev))
+ flags |= IFF_DORMANT;
+ }
return flags;
}
diff -X dontdiff -urp linux-2.6.14/net/core/link_watch.c linux-2.6.14-rfc2863/net/core/link_watch.c
--- linux-2.6.14/net/core/link_watch.c 2005-06-17 21:48:29.000000000 +0200
+++ linux-2.6.14-rfc2863/net/core/link_watch.c 2005-11-18 20:00:11.000000000 +0100
@@ -49,6 +49,34 @@ struct lw_event {
/* Avoid kmalloc() for most systems */
static struct lw_event singleevent;
+static inline unsigned char default_operstate(const struct net_device *dev) {
+ if (!netif_carrier_ok(dev)) return IF_OPER_DOWN;
+ if (netif_dormant(dev)) return IF_OPER_DORMANT;
+ return IF_OPER_UP;
+}
+
+
+static void rfc2863_policy(struct net_device *dev) {
+ unsigned char operstate = default_operstate(dev);
+ if (dev->oper_state) operstate = dev->oper_state(dev, operstate);
+
+ if (operstate == dev->operstate) return;
+
+ switch(dev->link_mode) {
+ case IF_LINK_MODE_DORMANT:
+ if (operstate == IF_OPER_UP) operstate = IF_OPER_DORMANT;
+ break;
+ case IF_LINK_MODE_DEFAULT:
+ default:
+ break;
+ }
+
+ write_lock_bh(&dev_base_lock);
+ dev->operstate = operstate;
+ write_unlock_bh(&dev_base_lock);
+}
+
+
/* Must be called with the rtnl semaphore held */
void linkwatch_run_queue(void)
{
@@ -81,6 +109,7 @@ void linkwatch_run_queue(void)
} else
dev_deactivate(dev);
+ rfc2863_policy(dev);
netdev_state_change(dev);
}
diff -X dontdiff -urp linux-2.6.14/net/core/rtnetlink.c linux-2.6.14-rfc2863/net/core/rtnetlink.c
--- linux-2.6.14/net/core/rtnetlink.c 2005-11-02 11:08:12.000000000 +0100
+++ linux-2.6.14-rfc2863/net/core/rtnetlink.c 2005-11-18 21:32:26.000000000 +0100
@@ -178,6 +178,34 @@ rtattr_failure:
}
+static void pseudo_set_operstate(struct net_device *dev, unsigned char transition) {
+ unsigned char operstate = dev->operstate;
+ ASSERT_RTNL();
+
+ switch(transition) {
+ case IF_OPER_UP:
+ if (operstate == IF_OPER_DORMANT) operstate = IF_OPER_UP;
+ break;
+ case IF_OPER_DORMANT:
+ if (operstate == IF_OPER_UP) operstate = IF_OPER_DORMANT;
+ break;
+ }
+
+ if (dev->operstate != operstate) {
+ write_lock_bh(&dev_base_lock);
+ dev->operstate = operstate;
+ write_unlock_bh(&dev_base_lock);
+ netdev_state_change(dev);
+ }
+}
+
+static void pseudo_set_linkmode(struct net_device *dev, unsigned char linkmode) {
+ ASSERT_RTNL();
+ write_lock_bh(&dev_base_lock);
+ dev->link_mode = linkmode;
+ write_unlock_bh(&dev_base_lock);
+}
+
static int rtnetlink_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
int type, u32 pid, u32 seq, u32 change,
unsigned int flags)
@@ -208,6 +236,13 @@ static int rtnetlink_fill_ifinfo(struct
}
if (1) {
+ u8 operstate = dev->operstate;
+ u8 link_mode = dev->link_mode;
+ RTA_PUT(skb, IFLA_OPERSTATE, sizeof(operstate), &operstate);
+ RTA_PUT(skb, IFLA_LINKMODE, sizeof(link_mode), &link_mode);
+ }
+
+ if (1) {
struct rtnl_link_ifmap map = {
.mem_start = dev->mem_start,
.mem_end = dev->mem_end,