diff options
Diffstat (limited to 'net/switchdev/switchdev.c')
-rw-r--r-- | net/switchdev/switchdev.c | 278 |
1 files changed, 5 insertions, 273 deletions
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c index a5fc9dd24aa9..02beb35f577f 100644 --- a/net/switchdev/switchdev.c +++ b/net/switchdev/switchdev.c | |||
@@ -21,7 +21,6 @@ | |||
21 | #include <linux/workqueue.h> | 21 | #include <linux/workqueue.h> |
22 | #include <linux/if_vlan.h> | 22 | #include <linux/if_vlan.h> |
23 | #include <linux/rtnetlink.h> | 23 | #include <linux/rtnetlink.h> |
24 | #include <net/ip_fib.h> | ||
25 | #include <net/switchdev.h> | 24 | #include <net/switchdev.h> |
26 | 25 | ||
27 | /** | 26 | /** |
@@ -344,8 +343,6 @@ static size_t switchdev_obj_size(const struct switchdev_obj *obj) | |||
344 | switch (obj->id) { | 343 | switch (obj->id) { |
345 | case SWITCHDEV_OBJ_ID_PORT_VLAN: | 344 | case SWITCHDEV_OBJ_ID_PORT_VLAN: |
346 | return sizeof(struct switchdev_obj_port_vlan); | 345 | return sizeof(struct switchdev_obj_port_vlan); |
347 | case SWITCHDEV_OBJ_ID_IPV4_FIB: | ||
348 | return sizeof(struct switchdev_obj_ipv4_fib); | ||
349 | case SWITCHDEV_OBJ_ID_PORT_FDB: | 346 | case SWITCHDEV_OBJ_ID_PORT_FDB: |
350 | return sizeof(struct switchdev_obj_port_fdb); | 347 | return sizeof(struct switchdev_obj_port_fdb); |
351 | case SWITCHDEV_OBJ_ID_PORT_MDB: | 348 | case SWITCHDEV_OBJ_ID_PORT_MDB: |
@@ -1042,7 +1039,7 @@ static int switchdev_port_fdb_dump_cb(struct switchdev_obj *obj) | |||
1042 | struct nlmsghdr *nlh; | 1039 | struct nlmsghdr *nlh; |
1043 | struct ndmsg *ndm; | 1040 | struct ndmsg *ndm; |
1044 | 1041 | ||
1045 | if (dump->idx < dump->cb->args[0]) | 1042 | if (dump->idx < dump->cb->args[2]) |
1046 | goto skip; | 1043 | goto skip; |
1047 | 1044 | ||
1048 | nlh = nlmsg_put(dump->skb, portid, seq, RTM_NEWNEIGH, | 1045 | nlh = nlmsg_put(dump->skb, portid, seq, RTM_NEWNEIGH, |
@@ -1089,7 +1086,7 @@ nla_put_failure: | |||
1089 | */ | 1086 | */ |
1090 | int switchdev_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb, | 1087 | int switchdev_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb, |
1091 | struct net_device *dev, | 1088 | struct net_device *dev, |
1092 | struct net_device *filter_dev, int idx) | 1089 | struct net_device *filter_dev, int *idx) |
1093 | { | 1090 | { |
1094 | struct switchdev_fdb_dump dump = { | 1091 | struct switchdev_fdb_dump dump = { |
1095 | .fdb.obj.orig_dev = dev, | 1092 | .fdb.obj.orig_dev = dev, |
@@ -1097,207 +1094,27 @@ int switchdev_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb, | |||
1097 | .dev = dev, | 1094 | .dev = dev, |
1098 | .skb = skb, | 1095 | .skb = skb, |
1099 | .cb = cb, | 1096 | .cb = cb, |
1100 | .idx = idx, | 1097 | .idx = *idx, |
1101 | }; | 1098 | }; |
1102 | int err; | 1099 | int err; |
1103 | 1100 | ||
1104 | err = switchdev_port_obj_dump(dev, &dump.fdb.obj, | 1101 | err = switchdev_port_obj_dump(dev, &dump.fdb.obj, |
1105 | switchdev_port_fdb_dump_cb); | 1102 | switchdev_port_fdb_dump_cb); |
1106 | cb->args[1] = err; | 1103 | *idx = dump.idx; |
1107 | return dump.idx; | 1104 | return err; |
1108 | } | 1105 | } |
1109 | EXPORT_SYMBOL_GPL(switchdev_port_fdb_dump); | 1106 | EXPORT_SYMBOL_GPL(switchdev_port_fdb_dump); |
1110 | 1107 | ||
1111 | static struct net_device *switchdev_get_lowest_dev(struct net_device *dev) | ||
1112 | { | ||
1113 | const struct switchdev_ops *ops = dev->switchdev_ops; | ||
1114 | struct net_device *lower_dev; | ||
1115 | struct net_device *port_dev; | ||
1116 | struct list_head *iter; | ||
1117 | |||
1118 | /* Recusively search down until we find a sw port dev. | ||
1119 | * (A sw port dev supports switchdev_port_attr_get). | ||
1120 | */ | ||
1121 | |||
1122 | if (ops && ops->switchdev_port_attr_get) | ||
1123 | return dev; | ||
1124 | |||
1125 | netdev_for_each_lower_dev(dev, lower_dev, iter) { | ||
1126 | port_dev = switchdev_get_lowest_dev(lower_dev); | ||
1127 | if (port_dev) | ||
1128 | return port_dev; | ||
1129 | } | ||
1130 | |||
1131 | return NULL; | ||
1132 | } | ||
1133 | |||
1134 | static struct net_device *switchdev_get_dev_by_nhs(struct fib_info *fi) | ||
1135 | { | ||
1136 | struct switchdev_attr attr = { | ||
1137 | .id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID, | ||
1138 | }; | ||
1139 | struct switchdev_attr prev_attr; | ||
1140 | struct net_device *dev = NULL; | ||
1141 | int nhsel; | ||
1142 | |||
1143 | ASSERT_RTNL(); | ||
1144 | |||
1145 | /* For this route, all nexthop devs must be on the same switch. */ | ||
1146 | |||
1147 | for (nhsel = 0; nhsel < fi->fib_nhs; nhsel++) { | ||
1148 | const struct fib_nh *nh = &fi->fib_nh[nhsel]; | ||
1149 | |||
1150 | if (!nh->nh_dev) | ||
1151 | return NULL; | ||
1152 | |||
1153 | dev = switchdev_get_lowest_dev(nh->nh_dev); | ||
1154 | if (!dev) | ||
1155 | return NULL; | ||
1156 | |||
1157 | attr.orig_dev = dev; | ||
1158 | if (switchdev_port_attr_get(dev, &attr)) | ||
1159 | return NULL; | ||
1160 | |||
1161 | if (nhsel > 0 && | ||
1162 | !netdev_phys_item_id_same(&prev_attr.u.ppid, &attr.u.ppid)) | ||
1163 | return NULL; | ||
1164 | |||
1165 | prev_attr = attr; | ||
1166 | } | ||
1167 | |||
1168 | return dev; | ||
1169 | } | ||
1170 | |||
1171 | /** | ||
1172 | * switchdev_fib_ipv4_add - Add/modify switch IPv4 route entry | ||
1173 | * | ||
1174 | * @dst: route's IPv4 destination address | ||
1175 | * @dst_len: destination address length (prefix length) | ||
1176 | * @fi: route FIB info structure | ||
1177 | * @tos: route TOS | ||
1178 | * @type: route type | ||
1179 | * @nlflags: netlink flags passed in (NLM_F_*) | ||
1180 | * @tb_id: route table ID | ||
1181 | * | ||
1182 | * Add/modify switch IPv4 route entry. | ||
1183 | */ | ||
1184 | int switchdev_fib_ipv4_add(u32 dst, int dst_len, struct fib_info *fi, | ||
1185 | u8 tos, u8 type, u32 nlflags, u32 tb_id) | ||
1186 | { | ||
1187 | struct switchdev_obj_ipv4_fib ipv4_fib = { | ||
1188 | .obj.id = SWITCHDEV_OBJ_ID_IPV4_FIB, | ||
1189 | .dst = dst, | ||
1190 | .dst_len = dst_len, | ||
1191 | .fi = fi, | ||
1192 | .tos = tos, | ||
1193 | .type = type, | ||
1194 | .nlflags = nlflags, | ||
1195 | .tb_id = tb_id, | ||
1196 | }; | ||
1197 | struct net_device *dev; | ||
1198 | int err = 0; | ||
1199 | |||
1200 | /* Don't offload route if using custom ip rules or if | ||
1201 | * IPv4 FIB offloading has been disabled completely. | ||
1202 | */ | ||
1203 | |||
1204 | #ifdef CONFIG_IP_MULTIPLE_TABLES | ||
1205 | if (fi->fib_net->ipv4.fib_has_custom_rules) | ||
1206 | return 0; | ||
1207 | #endif | ||
1208 | |||
1209 | if (fi->fib_net->ipv4.fib_offload_disabled) | ||
1210 | return 0; | ||
1211 | |||
1212 | dev = switchdev_get_dev_by_nhs(fi); | ||
1213 | if (!dev) | ||
1214 | return 0; | ||
1215 | |||
1216 | ipv4_fib.obj.orig_dev = dev; | ||
1217 | err = switchdev_port_obj_add(dev, &ipv4_fib.obj); | ||
1218 | if (!err) | ||
1219 | fi->fib_flags |= RTNH_F_OFFLOAD; | ||
1220 | |||
1221 | return err == -EOPNOTSUPP ? 0 : err; | ||
1222 | } | ||
1223 | EXPORT_SYMBOL_GPL(switchdev_fib_ipv4_add); | ||
1224 | |||
1225 | /** | ||
1226 | * switchdev_fib_ipv4_del - Delete IPv4 route entry from switch | ||
1227 | * | ||
1228 | * @dst: route's IPv4 destination address | ||
1229 | * @dst_len: destination address length (prefix length) | ||
1230 | * @fi: route FIB info structure | ||
1231 | * @tos: route TOS | ||
1232 | * @type: route type | ||
1233 | * @tb_id: route table ID | ||
1234 | * | ||
1235 | * Delete IPv4 route entry from switch device. | ||
1236 | */ | ||
1237 | int switchdev_fib_ipv4_del(u32 dst, int dst_len, struct fib_info *fi, | ||
1238 | u8 tos, u8 type, u32 tb_id) | ||
1239 | { | ||
1240 | struct switchdev_obj_ipv4_fib ipv4_fib = { | ||
1241 | .obj.id = SWITCHDEV_OBJ_ID_IPV4_FIB, | ||
1242 | .dst = dst, | ||
1243 | .dst_len = dst_len, | ||
1244 | .fi = fi, | ||
1245 | .tos = tos, | ||
1246 | .type = type, | ||
1247 | .nlflags = 0, | ||
1248 | .tb_id = tb_id, | ||
1249 | }; | ||
1250 | struct net_device *dev; | ||
1251 | int err = 0; | ||
1252 | |||
1253 | if (!(fi->fib_flags & RTNH_F_OFFLOAD)) | ||
1254 | return 0; | ||
1255 | |||
1256 | dev = switchdev_get_dev_by_nhs(fi); | ||
1257 | if (!dev) | ||
1258 | return 0; | ||
1259 | |||
1260 | ipv4_fib.obj.orig_dev = dev; | ||
1261 | err = switchdev_port_obj_del(dev, &ipv4_fib.obj); | ||
1262 | if (!err) | ||
1263 | fi->fib_flags &= ~RTNH_F_OFFLOAD; | ||
1264 | |||
1265 | return err == -EOPNOTSUPP ? 0 : err; | ||
1266 | } | ||
1267 | EXPORT_SYMBOL_GPL(switchdev_fib_ipv4_del); | ||
1268 | |||
1269 | /** | ||
1270 | * switchdev_fib_ipv4_abort - Abort an IPv4 FIB operation | ||
1271 | * | ||
1272 | * @fi: route FIB info structure | ||
1273 | */ | ||
1274 | void switchdev_fib_ipv4_abort(struct fib_info *fi) | ||
1275 | { | ||
1276 | /* There was a problem installing this route to the offload | ||
1277 | * device. For now, until we come up with more refined | ||
1278 | * policy handling, abruptly end IPv4 fib offloading for | ||
1279 | * for entire net by flushing offload device(s) of all | ||
1280 | * IPv4 routes, and mark IPv4 fib offloading broken from | ||
1281 | * this point forward. | ||
1282 | */ | ||
1283 | |||
1284 | fib_flush_external(fi->fib_net); | ||
1285 | fi->fib_net->ipv4.fib_offload_disabled = true; | ||
1286 | } | ||
1287 | EXPORT_SYMBOL_GPL(switchdev_fib_ipv4_abort); | ||
1288 | |||
1289 | bool switchdev_port_same_parent_id(struct net_device *a, | 1108 | bool switchdev_port_same_parent_id(struct net_device *a, |
1290 | struct net_device *b) | 1109 | struct net_device *b) |
1291 | { | 1110 | { |
1292 | struct switchdev_attr a_attr = { | 1111 | struct switchdev_attr a_attr = { |
1293 | .orig_dev = a, | 1112 | .orig_dev = a, |
1294 | .id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID, | 1113 | .id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID, |
1295 | .flags = SWITCHDEV_F_NO_RECURSE, | ||
1296 | }; | 1114 | }; |
1297 | struct switchdev_attr b_attr = { | 1115 | struct switchdev_attr b_attr = { |
1298 | .orig_dev = b, | 1116 | .orig_dev = b, |
1299 | .id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID, | 1117 | .id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID, |
1300 | .flags = SWITCHDEV_F_NO_RECURSE, | ||
1301 | }; | 1118 | }; |
1302 | 1119 | ||
1303 | if (switchdev_port_attr_get(a, &a_attr) || | 1120 | if (switchdev_port_attr_get(a, &a_attr) || |
@@ -1306,89 +1123,4 @@ bool switchdev_port_same_parent_id(struct net_device *a, | |||
1306 | 1123 | ||
1307 | return netdev_phys_item_id_same(&a_attr.u.ppid, &b_attr.u.ppid); | 1124 | return netdev_phys_item_id_same(&a_attr.u.ppid, &b_attr.u.ppid); |
1308 | } | 1125 | } |
1309 | |||
1310 | static u32 switchdev_port_fwd_mark_get(struct net_device *dev, | ||
1311 | struct net_device *group_dev) | ||
1312 | { | ||
1313 | struct net_device *lower_dev; | ||
1314 | struct list_head *iter; | ||
1315 | |||
1316 | netdev_for_each_lower_dev(group_dev, lower_dev, iter) { | ||
1317 | if (lower_dev == dev) | ||
1318 | continue; | ||
1319 | if (switchdev_port_same_parent_id(dev, lower_dev)) | ||
1320 | return lower_dev->offload_fwd_mark; | ||
1321 | return switchdev_port_fwd_mark_get(dev, lower_dev); | ||
1322 | } | ||
1323 | |||
1324 | return dev->ifindex; | ||
1325 | } | ||
1326 | EXPORT_SYMBOL_GPL(switchdev_port_same_parent_id); | 1126 | EXPORT_SYMBOL_GPL(switchdev_port_same_parent_id); |
1327 | |||
1328 | static void switchdev_port_fwd_mark_reset(struct net_device *group_dev, | ||
1329 | u32 old_mark, u32 *reset_mark) | ||
1330 | { | ||
1331 | struct net_device *lower_dev; | ||
1332 | struct list_head *iter; | ||
1333 | |||
1334 | netdev_for_each_lower_dev(group_dev, lower_dev, iter) { | ||
1335 | if (lower_dev->offload_fwd_mark == old_mark) { | ||
1336 | if (!*reset_mark) | ||
1337 | *reset_mark = lower_dev->ifindex; | ||
1338 | lower_dev->offload_fwd_mark = *reset_mark; | ||
1339 | } | ||
1340 | switchdev_port_fwd_mark_reset(lower_dev, old_mark, reset_mark); | ||
1341 | } | ||
1342 | } | ||
1343 | |||
1344 | /** | ||
1345 | * switchdev_port_fwd_mark_set - Set port offload forwarding mark | ||
1346 | * | ||
1347 | * @dev: port device | ||
1348 | * @group_dev: containing device | ||
1349 | * @joining: true if dev is joining group; false if leaving group | ||
1350 | * | ||
1351 | * An ungrouped port's offload mark is just its ifindex. A grouped | ||
1352 | * port's (member of a bridge, for example) offload mark is the ifindex | ||
1353 | * of one of the ports in the group with the same parent (switch) ID. | ||
1354 | * Ports on the same device in the same group will have the same mark. | ||
1355 | * | ||
1356 | * Example: | ||
1357 | * | ||
1358 | * br0 ifindex=9 | ||
1359 | * sw1p1 ifindex=2 mark=2 | ||
1360 | * sw1p2 ifindex=3 mark=2 | ||
1361 | * sw2p1 ifindex=4 mark=5 | ||
1362 | * sw2p2 ifindex=5 mark=5 | ||
1363 | * | ||
1364 | * If sw2p2 leaves the bridge, we'll have: | ||
1365 | * | ||
1366 | * br0 ifindex=9 | ||
1367 | * sw1p1 ifindex=2 mark=2 | ||
1368 | * sw1p2 ifindex=3 mark=2 | ||
1369 | * sw2p1 ifindex=4 mark=4 | ||
1370 | * sw2p2 ifindex=5 mark=5 | ||
1371 | */ | ||
1372 | void switchdev_port_fwd_mark_set(struct net_device *dev, | ||
1373 | struct net_device *group_dev, | ||
1374 | bool joining) | ||
1375 | { | ||
1376 | u32 mark = dev->ifindex; | ||
1377 | u32 reset_mark = 0; | ||
1378 | |||
1379 | if (group_dev) { | ||
1380 | ASSERT_RTNL(); | ||
1381 | if (joining) | ||
1382 | mark = switchdev_port_fwd_mark_get(dev, group_dev); | ||
1383 | else if (dev->offload_fwd_mark == mark) | ||
1384 | /* Ohoh, this port was the mark reference port, | ||
1385 | * but it's leaving the group, so reset the | ||
1386 | * mark for the remaining ports in the group. | ||
1387 | */ | ||
1388 | switchdev_port_fwd_mark_reset(group_dev, mark, | ||
1389 | &reset_mark); | ||
1390 | } | ||
1391 | |||
1392 | dev->offload_fwd_mark = mark; | ||
1393 | } | ||
1394 | EXPORT_SYMBOL_GPL(switchdev_port_fwd_mark_set); | ||