#include <linux/kernel.h>
#include <asm/uaccess.h>
-#include <linux/netdevice.h>
+#include <linux/completion.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
+#include <linux/netdevice.h>
#include <net/genetlink.h>
#include "compat.h"
static struct genl_family brc_genl_family;
static struct genl_multicast_group brc_mc_group;
+/* Completion for vswitchd to notify the ioctl that the operation
+ * completed. */
+DECLARE_COMPLETION(dp_act_done);
+
+/* Time to wait for vswitchd to respond to a datapath action (in
+ * milliseconds) */
+#define DP_ACT_TIMEOUT 5000
+
+/* Positive errno as a result of a datapath action. Calls that make
+ * use of this variable are serialized by the br_ioctl_mutex. */
+int dp_act_err;
+
int brc_send_dp_add_del(const char *dp_name, int add);
-int brc_send_port_add_del(const char *dp_name, const char *port_name, int add);
+int brc_send_port_add_del(struct net_device *dev, struct net_device *port,
+ int add);
/* Called with RTNL lock. */
static int
rcu_read_unlock();
}
-/* Legacy deviceless bridge ioctl's. We only use this to provide
- * support for "brctl show". */
+/* Legacy deviceless bridge ioctl's. The primary reason to do this
+ * is to provide support for 'brctl show' without having to implement
+ * the bridge's sysfs system. Also, 'brctl' will fall back on the
+ * legacy device ioctl's if an attempt to add or delete a bridge results in
+ * an error. By re-attempting the failed add or delete call, the error
+ * messages from 'brctl' are meaningful. */
static int
old_deviceless(void __user *uarg)
{
kfree(indices);
return ret;
}
+
+ case BRCTL_DEL_BRIDGE:
+ case BRCTL_ADD_BRIDGE: {
+ char dp_name[IFNAMSIZ];
+
+ if (copy_from_user(dp_name, (void __user *)args[1], IFNAMSIZ))
+ return -EFAULT;
+
+ dp_name[IFNAMSIZ-1] = 0;
+ if (args[0] == BRCTL_ADD_BRIDGE)
+ return brc_send_dp_add_del(dp_name, 1);
+
+ return brc_send_dp_add_del(dp_name, 0);
+ }
}
return -EOPNOTSUPP;
}
+/* Called with the br_ioctl_mutex. */
static int
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,23)
brc_ioctl_deviceless_stub(unsigned int cmd, void __user *uarg)
{
switch (cmd) {
case SIOCGIFBR:
+ case SIOCSIFBR:
return old_deviceless(uarg);
case SIOCBRADDBR:
return -EOPNOTSUPP;
}
-/* Legacy ioctl's through SIOCDEVPRIVATE. We only use this to provide
- * support for "brctl show". */
+/* Legacy ioctl's through SIOCDEVPRIVATE. The primary reason to do this
+ * is to provide support for 'brctl show' without having to implement
+ * the bridge's sysfs system. Also, 'brctl' will fall back on the
+ * legacy device ioctl's if an attempt to modify an interface results in
+ * an error. By re-attempting the failed modify call, the error messages
+ * from 'brctl' are meaningful. */
static int
old_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
return -EFAULT;
switch (args[0]) {
+ case BRCTL_ADD_IF:
+ case BRCTL_DEL_IF: {
+ struct net_device *port;
+ int err;
+
+ port = dev_get_by_index(&init_net, args[1]);
+ if (!port)
+ return -EINVAL;
+ err = brc_send_port_add_del(dev, port, args[0] == BRCTL_ADD_IF);
+ dev_put(port);
+ return err;
+ }
+
case BRCTL_GET_BRIDGE_INFO: {
struct __bridge_info b;
uint64_t id = 0;
memset(&b, 0, sizeof(struct __bridge_info));
- /* xxx Mutex Locking? */
rcu_read_lock();
for (i=0; i<ETH_ALEN; i++)
id |= (uint64_t)dev->dev_addr[i] << (8*(ETH_ALEN-1 - i));
port = dev_get_by_index(&init_net, rq->ifr_ifindex);
if (!port)
return -EINVAL;
- err = brc_send_port_add_del(dev->name, port->name,
- cmd == SIOCBRADDIF);
+ err = brc_send_port_add_del(dev, port, cmd == SIOCBRADDIF);
dev_put(port);
break;
}
.dumpit = NULL
};
+/* Attribute policy: what each attribute may contain. */
+static struct nla_policy brc_genl_policy[BRC_GENL_A_MAX + 1] = {
+ [BRC_GENL_A_ERR_CODE] = { .type = NLA_U32 }
+};
+
+static int
+brc_genl_dp_result(struct sk_buff *skb, struct genl_info *info)
+{
+ dp_act_err = nla_get_u32(info->attrs[BRC_GENL_A_ERR_CODE]);
+ complete(&dp_act_done);
+
+ return 0;
+}
+
+static struct genl_ops brc_genl_ops_dp_result = {
+ .cmd = BRC_GENL_C_DP_RESULT,
+ .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privelege. */
+ .policy = brc_genl_policy,
+ .doit = brc_genl_dp_result,
+ .dumpit = NULL
+};
+
int brc_send_dp_add_del(const char *dp_name, int add)
{
struct sk_buff *skb;
void *data;
+ int retval;
skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (skb == NULL)
NLA_PUT_STRING(skb, BRC_GENL_A_DP_NAME, dp_name);
+ init_completion(&dp_act_done);
+
genlmsg_end(skb, data);
- return genlmsg_multicast(skb, 0, brc_mc_group.id, GFP_ATOMIC);
+ retval = genlmsg_multicast(skb, 0, brc_mc_group.id, GFP_ATOMIC);
+ if (retval < 0)
+ return retval;
+
+ if (!wait_for_completion_timeout(&dp_act_done,
+ msecs_to_jiffies(DP_ACT_TIMEOUT)))
+ return -EIO;
+
+ /* The value is returned as a positive errno, so make it negative */
+ if (dp_act_err)
+ return -dp_act_err;
+
+ return 0;
nla_put_failure:
err:
return -EINVAL;
}
-int brc_send_port_add_del(const char *dp_name, const char *port_name, int add)
+int brc_send_port_add_del(struct net_device *dev, struct net_device *port,
+ int add)
{
+ struct dp_dev *dp_dev = netdev_priv(dev);
+ struct datapath *dp = dp_dev->dp;
struct sk_buff *skb;
void *data;
if (skb == NULL)
return -ENOMEM;
- if (add)
+ if (add) {
+ /* Only add the port if it's not attached to a datapath. */
+ if (port->br_port != NULL) {
+ kfree_skb(skb);
+ return -EBUSY;
+ }
data = genlmsg_put(skb, 0, 0, &brc_genl_family, 0,
BRC_GENL_C_PORT_ADD);
- else
+ } else {
+ /* Only delete the port if it's attached to this datapath. */
+ if (port->br_port == NULL || port->br_port->dp != dp) {
+ kfree_skb(skb);
+ return -ENOENT;
+ }
data = genlmsg_put(skb, 0, 0, &brc_genl_family, 0,
BRC_GENL_C_PORT_DEL);
+ }
if (!data)
goto err;
- NLA_PUT_STRING(skb, BRC_GENL_A_DP_NAME, dp_name);
- NLA_PUT_STRING(skb, BRC_GENL_A_PORT_NAME, port_name);
+ NLA_PUT_STRING(skb, BRC_GENL_A_DP_NAME, dev->name);
+ NLA_PUT_STRING(skb, BRC_GENL_A_PORT_NAME, port->name);
genlmsg_end(skb, data);
return genlmsg_multicast(skb, 0, brc_mc_group.id, GFP_ATOMIC);
if (err != 0)
goto err_unregister;
+ err = genl_register_ops(&brc_genl_family, &brc_genl_ops_dp_result);
+ if (err != 0)
+ goto err_unregister;
+
strcpy(brc_mc_group.name, "brcompat");
err = genl_register_mc_group(&brc_genl_family, &brc_mc_group);
if (err < 0)
/* Attributes that can be attached to the datapath's netlink messages. */
enum {
BRC_GENL_A_UNSPEC,
- BRC_GENL_A_DP_NAME, /* Datapath name. */
- BRC_GENL_A_PORT_NAME, /* Interface name. */
- BRC_GENL_A_MC_GROUP, /* Generic netlink multicast group. */
+ BRC_GENL_A_DP_NAME, /* Datapath name. */
+ BRC_GENL_A_PORT_NAME, /* Interface name. */
+ BRC_GENL_A_ERR_CODE, /* Positive error code. */
+ BRC_GENL_A_MC_GROUP, /* Generic netlink multicast group. */
__BRC_GENL_A_MAX,
BRC_GENL_A_MAX = __BRC_GENL_A_MAX - 1
/* Commands that can be executed on the datapath's netlink interface. */
enum brc_genl_command {
BRC_GENL_C_UNSPEC,
- BRC_GENL_C_DP_ADD, /* Datapath created. */
- BRC_GENL_C_DP_DEL, /* Datapath destroyed. */
- BRC_GENL_C_PORT_ADD, /* Port added to datapath. */
- BRC_GENL_C_PORT_DEL, /* Port removed from datapath. */
- BRC_GENL_C_QUERY_MC, /* Get multicast group for brcompat. */
+ BRC_GENL_C_DP_ADD, /* Datapath created. */
+ BRC_GENL_C_DP_DEL, /* Datapath destroyed. */
+ BRC_GENL_C_DP_RESULT, /* Result of datapath command from vswitchd. */
+ BRC_GENL_C_PORT_ADD, /* Port added to datapath. */
+ BRC_GENL_C_PORT_DEL, /* Port removed from datapath. */
+ BRC_GENL_C_QUERY_MC, /* Get multicast group for brcompat. */
__BRC_GENL_C_MAX,
BRC_GENL_C_MAX = __BRC_GENL_C_MAX - 1
brc_add_dp(const char *dp_name)
{
if (bridge_exists(dp_name)) {
- return EINVAL;
+ return EEXIST;
}
brc_modify_config(dp_name, NULL, BMC_ADD_DP);
brc_del_dp(const char *dp_name)
{
if (!bridge_exists(dp_name)) {
- return EINVAL;
+ return ENXIO;
}
brc_modify_config(dp_name, NULL, BMC_DEL_DP);
int
brc_handle_dp_cmd(struct ofpbuf *buffer, bool add)
{
+ int dp_act_err;
+ int retval;
struct nlattr *attrs[ARRAY_SIZE(brc_dp_policy)];
const char *dp_name;
+ struct ofpbuf msg;
if (!nl_policy_parse(buffer, brc_dp_policy, attrs,
ARRAY_SIZE(brc_dp_policy))) {
dp_name = nl_attr_get(attrs[BRC_GENL_A_DP_NAME]);
if (add) {
- return brc_add_dp(dp_name);
+ dp_act_err = brc_add_dp(dp_name);
} else {
- return brc_del_dp(dp_name);
+ dp_act_err = brc_del_dp(dp_name);
}
+
+ /* Notify the brcompat kernel module of the result. */
+ ofpbuf_init(&msg, 0);
+ nl_msg_put_genlmsghdr(&msg, nl_sock, 32, brc_family, NLM_F_REQUEST,
+ BRC_GENL_C_DP_RESULT, 1);
+ nl_msg_put_u32(&msg, BRC_GENL_A_ERR_CODE, dp_act_err);
+ retval = nl_sock_send(nl_sock, &msg, false);
+ if (retval) {
+ VLOG_WARN_RL(&rl, "brc_handle_dp_cmd: %s", strerror(retval));
+ }
+ ofpbuf_uninit(&msg);
+
+ return dp_act_err;
}
static const struct nl_policy brc_port_policy[] = {