2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-03 07:45:30 +00:00

lib: Show tunnel egress interface in ovsdb

This commit parses rtnetlink address notifications from the
kernel in order to display the egress interface of tunnels in the
database.

Bug #4103.
This commit is contained in:
Ethan Jackson
2010-12-21 16:26:21 -08:00
parent 21d6e22eee
commit ea83a2fcd0
14 changed files with 566 additions and 13 deletions

View File

@@ -191,7 +191,9 @@ lib_libopenvswitch_a_SOURCES += \
lib/rtnetlink.c \
lib/rtnetlink.h \
lib/rtnetlink-link.c \
lib/rtnetlink-link.h
lib/rtnetlink-link.h \
lib/rtnetlink-route.c \
lib/rtnetlink-route.h
endif
if HAVE_OPENSSL

View File

@@ -320,6 +320,7 @@ static const struct netdev_class dummy_class = {
NULL, /* get_in6 */
NULL, /* add_router */
NULL, /* get_next_hop */
NULL, /* get_tnl_iface */
NULL, /* arp_lookup */
netdev_dummy_update_flags,

View File

@@ -2176,6 +2176,7 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
netdev_linux_get_in6, \
netdev_linux_add_router, \
netdev_linux_get_next_hop, \
NULL, /* get_tnl_iface */ \
netdev_linux_arp_lookup, \
\
netdev_linux_update_flags, \

View File

@@ -518,6 +518,17 @@ struct netdev_class {
int (*get_next_hop)(const struct in_addr *host, struct in_addr *next_hop,
char **netdev_name);
/* Looks up the name of the interface out of which traffic will egress if
* 'netdev' is a tunnel. If unsuccessful, or 'netdev' is not a tunnel,
* will return null. This function does not necessarily return the
* physical interface out which traffic will egress. Instead it returns
* the interface which is assigned 'netdev's remote_ip. This may be an
* internal interface such as a bridge port.
*
* This function may be set to null if 'netdev' is not a tunnel or it is
* not supported. */
const char *(*get_tnl_iface)(const struct netdev *netdev);
/* Looks up the ARP table entry for 'ip' on 'netdev' and stores the
* corresponding MAC address in 'mac'. A return value of ENXIO, in
* particular, indicates that there is no ARP table entry for 'ip' on

View File

@@ -20,21 +20,50 @@
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include "byte-order.h"
#include "hash.h"
#include "hmap.h"
#include "list.h"
#include "netdev-provider.h"
#include "netlink.h"
#include "netlink-socket.h"
#include "ofpbuf.h"
#include "openvswitch/datapath-protocol.h"
#include "openvswitch/tunnel.h"
#include "packets.h"
#include "rtnetlink.h"
#include "rtnetlink-route.h"
#include "rtnetlink-link.h"
#include "shash.h"
#include "socket-util.h"
#include "vlog.h"
VLOG_DEFINE_THIS_MODULE(netdev_vport);
static struct hmap name_map;
static struct hmap route_map;
static struct rtnetlink_notifier netdev_vport_link_notifier;
static struct rtnetlink_notifier netdev_vport_route_notifier;
struct route_node {
struct hmap_node node; /* Node in route_map. */
int rta_oif; /* Egress interface index. */
uint32_t rta_dst; /* Destination address in host byte order. */
unsigned char rtm_dst_len; /* Destination address length. */
};
struct name_node {
struct hmap_node node; /* Node in name_map. */
uint32_t ifi_index; /* Kernel interface index. */
char ifname[IFNAMSIZ]; /* Interface name. */
};
struct netdev_vport_notifier {
struct netdev_notifier notifier;
struct list list_node;
@@ -66,6 +95,12 @@ static int netdev_vport_create(const struct netdev_class *, const char *,
const struct shash *, struct netdev_dev **);
static void netdev_vport_poll_notify(const struct netdev *);
static void netdev_vport_tnl_iface_init(void);
static void netdev_vport_route_change(const struct rtnetlink_route_change *,
void *);
static void netdev_vport_link_change(const struct rtnetlink_link_change *,
void *);
static bool
is_vport_class(const struct netdev_class *class)
{
@@ -107,6 +142,13 @@ netdev_vport_get_config(const struct netdev *netdev, void *config)
}
}
static int
netdev_vport_init(void)
{
netdev_vport_tnl_iface_init();
return 0;
}
static int
netdev_vport_create(const struct netdev_class *netdev_class, const char *name,
const struct shash *args,
@@ -387,6 +429,284 @@ netdev_vport_poll_remove(struct netdev_notifier *notifier_)
free(notifier);
}
static void
netdev_vport_run(void)
{
rtnetlink_link_notifier_run();
rtnetlink_route_notifier_run();
}
static void
netdev_vport_wait(void)
{
rtnetlink_link_notifier_wait();
rtnetlink_route_notifier_wait();
}
/* get_tnl_iface() implementation. */
static struct name_node *
name_node_lookup(int ifi_index)
{
struct name_node *nn;
HMAP_FOR_EACH_WITH_HASH(nn, node, hash_int(ifi_index, 0), &name_map) {
if (nn->ifi_index == ifi_index) {
return nn;
}
}
return NULL;
}
static struct route_node *
route_node_lookup(int rta_oif, uint32_t rta_dst, unsigned char rtm_dst_len)
{
uint32_t hash;
struct route_node *rn;
hash = hash_3words(rta_oif, rta_dst, rtm_dst_len);
HMAP_FOR_EACH_WITH_HASH(rn, node, hash, &route_map) {
if (rn->rta_oif == rn->rta_oif &&
rn->rta_dst == rn->rta_dst &&
rn->rtm_dst_len == rn->rtm_dst_len) {
return rn;
}
}
return NULL;
}
/* Resets the name or route map depending on the value of 'is_name'. Clears
* the appropriate map, makes an rtnetlink dump request, and calls the change
* callback for each reply from the kernel. One should probably use
* netdev_vport_reset_routes or netdev_vport_reset_names instead. */
static int
netdev_vport_reset_name_else_route(bool is_name)
{
int error;
int nlmsg_type;
struct nl_dump dump;
struct rtgenmsg *rtmsg;
struct ofpbuf request, reply;
static struct nl_sock *rtnl_sock;
if (is_name) {
struct name_node *nn, *nn_next;
HMAP_FOR_EACH_SAFE(nn, nn_next, node, &name_map) {
hmap_remove(&name_map, &nn->node);
free(nn);
}
} else {
struct route_node *rn, *rn_next;
HMAP_FOR_EACH_SAFE(rn, rn_next, node, &route_map) {
hmap_remove(&route_map, &rn->node);
free(rn);
}
}
error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0, &rtnl_sock);
if (error) {
VLOG_WARN_RL(&rl, "Failed to create NETLINK_ROUTE socket");
return error;
}
ofpbuf_init(&request, 0);
nlmsg_type = is_name ? RTM_GETLINK : RTM_GETROUTE;
nl_msg_put_nlmsghdr(&request, sizeof *rtmsg, nlmsg_type, NLM_F_REQUEST);
rtmsg = ofpbuf_put_zeros(&request, sizeof *rtmsg);
rtmsg->rtgen_family = AF_INET;
nl_dump_start(&dump, rtnl_sock, &request);
while (nl_dump_next(&dump, &reply)) {
if (is_name) {
struct rtnetlink_link_change change;
if (rtnetlink_link_parse(&reply, &change)) {
netdev_vport_link_change(&change, NULL);
}
} else {
struct rtnetlink_route_change change;
if (rtnetlink_route_parse(&reply, &change)) {
netdev_vport_route_change(&change, NULL);
}
}
}
error = nl_dump_done(&dump);
nl_sock_destroy(rtnl_sock);
return error;
}
static int
netdev_vport_reset_routes(void)
{
return netdev_vport_reset_name_else_route(false);
}
static int
netdev_vport_reset_names(void)
{
return netdev_vport_reset_name_else_route(true);
}
static void
netdev_vport_route_change(const struct rtnetlink_route_change *change,
void *aux OVS_UNUSED)
{
if (!change) {
netdev_vport_reset_routes();
} else if (change->nlmsg_type == RTM_NEWROUTE) {
uint32_t hash;
struct route_node *rn;
if (route_node_lookup(change->rta_oif, change->rta_dst,
change->rtm_dst_len)) {
return;
}
rn = xzalloc(sizeof *rn);
rn->rta_oif = change->rta_oif;
rn->rta_dst = change->rta_dst;
rn->rtm_dst_len = change->rtm_dst_len;
hash = hash_3words(rn->rta_oif, rn->rta_dst, rn->rtm_dst_len);
hmap_insert(&route_map, &rn->node, hash);
} else if (change->nlmsg_type == RTM_DELROUTE) {
struct route_node *rn;
rn = route_node_lookup(change->rta_oif, change->rta_dst,
change->rtm_dst_len);
if (rn) {
hmap_remove(&route_map, &rn->node);
free(rn);
}
} else {
VLOG_WARN_RL(&rl, "Received unexpected rtnetlink message type %d",
change->nlmsg_type);
}
}
static void
netdev_vport_link_change(const struct rtnetlink_link_change *change,
void *aux OVS_UNUSED)
{
if (!change) {
netdev_vport_reset_names();
} else if (change->nlmsg_type == RTM_NEWLINK) {
struct name_node *nn;
if (name_node_lookup(change->ifi_index)) {
return;
}
nn = xzalloc(sizeof *nn);
nn->ifi_index = change->ifi_index;
strncpy(nn->ifname, change->ifname, IFNAMSIZ);
nn->ifname[IFNAMSIZ - 1] = '\0';
hmap_insert(&name_map, &nn->node, hash_int(nn->ifi_index, 0));
} else if (change->nlmsg_type == RTM_DELLINK) {
struct name_node *nn;
nn = name_node_lookup(change->ifi_index);
if (nn) {
hmap_remove(&name_map, &nn->node);
free(nn);
}
/* Link deletions do not result in all of the RTM_DELROUTE messages one
* would expect. For now, go ahead and reset route_map whenever a link
* is deleted. */
netdev_vport_reset_routes();
} else {
VLOG_WARN_RL(&rl, "Received unexpected rtnetlink message type %d",
change->nlmsg_type);
}
}
static void
netdev_vport_tnl_iface_init(void)
{
static bool tnl_iface_is_init = false;
if (!tnl_iface_is_init) {
hmap_init(&name_map);
hmap_init(&route_map);
rtnetlink_link_notifier_register(&netdev_vport_link_notifier,
netdev_vport_link_change, NULL);
rtnetlink_route_notifier_register(&netdev_vport_route_notifier,
netdev_vport_route_change, NULL);
netdev_vport_reset_names();
netdev_vport_reset_routes();
tnl_iface_is_init = true;
}
}
static const char *
netdev_vport_get_tnl_iface(const struct netdev *netdev)
{
int dst_len;
uint32_t route;
struct netdev_dev_vport *ndv;
struct tnl_port_config *config;
struct route_node *rn, *rn_def, *rn_iter;
ndv = netdev_dev_vport_cast(netdev_get_dev(netdev));
config = (struct tnl_port_config *) ndv->config;
route = ntohl(config->daddr);
dst_len = 0;
rn = NULL;
rn_def = NULL;
HMAP_FOR_EACH(rn_iter, node, &route_map) {
if (rn_iter->rtm_dst_len == 0 && rn_iter->rta_dst == 0) {
/* Default route. */
rn_def = rn_iter;
} else if (rn_iter->rtm_dst_len > dst_len) {
uint32_t mask = 0xffffffff << (32 - rn_iter->rtm_dst_len);
if ((route & mask) == (rn_iter->rta_dst & mask)) {
rn = rn_iter;
dst_len = rn_iter->rtm_dst_len;
}
}
}
if (!rn) {
rn = rn_def;
}
if (rn) {
uint32_t hash;
struct name_node *nn;
hash = hash_int(rn->rta_oif, 0);
HMAP_FOR_EACH_WITH_HASH(nn, node, hash, &name_map) {
if (nn->ifi_index == rn->rta_oif) {
return nn->ifname;
}
}
}
return NULL;
}
/* Helper functions. */
@@ -536,7 +856,7 @@ parse_tunnel_config(const struct netdev_dev *dev, const struct shash *args,
}
} else if (!strcmp(node->name, "psk") && is_ipsec) {
ipsec_mech_set = true;
} else if (is_ipsec
} else if (is_ipsec
&& (!strcmp(node->name, "certificate")
|| !strcmp(node->name, "private_key")
|| !strcmp(node->name, "use_ssl_cert"))) {
@@ -604,10 +924,10 @@ parse_patch_config(const struct netdev_dev *dev, const struct shash *args,
return 0;
}
#define VPORT_FUNCTIONS \
NULL, /* init */ \
NULL, /* run */ \
NULL, /* wait */ \
#define VPORT_FUNCTIONS(TNL_IFACE) \
netdev_vport_init, \
netdev_vport_run, \
netdev_vport_wait, \
\
netdev_vport_create, \
netdev_vport_destroy, \
@@ -654,6 +974,7 @@ parse_patch_config(const struct netdev_dev *dev, const struct shash *args,
NULL, /* get_in6 */ \
NULL, /* add_router */ \
NULL, /* get_next_hop */ \
TNL_IFACE, \
NULL, /* arp_lookup */ \
\
netdev_vport_update_flags, \
@@ -665,10 +986,13 @@ void
netdev_vport_register(void)
{
static const struct vport_class vport_classes[] = {
{ { "gre", VPORT_FUNCTIONS }, parse_tunnel_config },
{ { "ipsec_gre", VPORT_FUNCTIONS }, parse_tunnel_config },
{ { "capwap", VPORT_FUNCTIONS }, parse_tunnel_config },
{ { "patch", VPORT_FUNCTIONS }, parse_patch_config }
{ { "gre", VPORT_FUNCTIONS(netdev_vport_get_tnl_iface) },
parse_tunnel_config },
{ { "ipsec_gre", VPORT_FUNCTIONS(netdev_vport_get_tnl_iface) },
parse_tunnel_config },
{ { "capwap", VPORT_FUNCTIONS(netdev_vport_get_tnl_iface) },
parse_tunnel_config },
{ { "patch", VPORT_FUNCTIONS(NULL) }, parse_patch_config }
};
int i;

View File

@@ -769,6 +769,16 @@ netdev_get_next_hop(const struct netdev *netdev,
return error;
}
const char *
netdev_get_tnl_iface(const struct netdev *netdev)
{
struct netdev_dev *dev = netdev_get_dev(netdev);
return (dev->netdev_class->get_tnl_iface
? dev->netdev_class->get_tnl_iface(netdev)
: NULL);
}
/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address and
* returns 0. Otherwise, returns a positive errno value and sets '*in6' to
* all-zero-bits (in6addr_any).

View File

@@ -141,6 +141,7 @@ int netdev_get_in6(const struct netdev *, struct in6_addr *);
int netdev_add_router(struct netdev *, struct in_addr router);
int netdev_get_next_hop(const struct netdev *, const struct in_addr *host,
struct in_addr *next_hop, char **);
const char *netdev_get_tnl_iface(const struct netdev *);
int netdev_arp_lookup(const struct netdev *, uint32_t ip, uint8_t mac[6]);
int netdev_get_flags(const struct netdev *, enum netdev_flags *);

View File

@@ -29,7 +29,10 @@
static struct rtnetlink *rtn = NULL;
static struct rtnetlink_link_change rtn_change;
static bool
/* Parses a rtnetlink message 'buf' into 'change'. If 'buf' is unparseable,
* leaves 'change' untouched and returns false. Otherwise, populates 'change'
* and returns true. */
bool
rtnetlink_link_parse(struct ofpbuf *buf,
struct rtnetlink_link_change *change)
{

View File

@@ -17,6 +17,9 @@
#ifndef RTNETLINK_LINK_H
#define RTNETLINK_LINK_H 1
#include <stdbool.h>
struct ofpbuf;
struct rtnetlink_notifier;
/* These functions are Linux specific, so they should be used directly only by
@@ -45,6 +48,8 @@ typedef
void rtnetlink_link_notify_func(const struct rtnetlink_link_change *change,
void *aux);
bool rtnetlink_link_parse(struct ofpbuf *buf,
struct rtnetlink_link_change *change);
int rtnetlink_link_notifier_register(struct rtnetlink_notifier *,
rtnetlink_link_notify_func *, void *aux);
void rtnetlink_link_notifier_unregister(struct rtnetlink_notifier *);

119
lib/rtnetlink-route.c Normal file
View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2009, 2010 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <config.h>
#include "rtnetlink-route.h"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include "netlink.h"
#include "ofpbuf.h"
#include "rtnetlink.h"
static struct rtnetlink *rtn = NULL;
static struct rtnetlink_route_change rtn_change;
/* Parses a rtnetlink message 'buf' into 'change'. If 'buf' is unparseable,
* leaves 'change' untouched and returns false. Otherwise, populates 'change'
* and returns true. */
bool
rtnetlink_route_parse(struct ofpbuf *buf,
struct rtnetlink_route_change *change)
{
bool parsed;
static const struct nl_policy policy[] = {
[RTA_DST] = { .type = NL_A_U32, .optional = true },
[RTA_OIF] = { .type = NL_A_U32, .optional = false },
};
static struct nlattr *attrs[ARRAY_SIZE(policy)];
parsed = nl_policy_parse(buf, NLMSG_HDRLEN + sizeof(struct rtmsg),
policy, attrs, ARRAY_SIZE(policy));
if (parsed) {
const struct nlmsghdr *nlmsg;
const struct rtmsg *rtm;
nlmsg = buf->data;
rtm = (const struct rtmsg *) ((const char *) buf->data + NLMSG_HDRLEN);
if (rtm->rtm_family != AF_INET) {
return false;
}
change->nlmsg_type = nlmsg->nlmsg_type;
change->rtm_dst_len = rtm->rtm_dst_len;
change->rta_oif = nl_attr_get_u32(attrs[RTA_OIF]);
change->rta_dst = (attrs[RTA_DST]
? ntohl(nl_attr_get_be32(attrs[RTA_DST]))
: 0);
}
return parsed;
}
/* Registers 'cb' to be called with auxiliary data 'aux' with route change
* notifications. The notifier is stored in 'notifier', which callers must
* not modify or free.
*
* Returns 0 if successful, otherwise a positive errno value. */
int
rtnetlink_route_notifier_register(struct rtnetlink_notifier *notifier,
rtnetlink_route_notify_func *cb, void *aux)
{
rtnetlink_parse_func *pf = (rtnetlink_parse_func *) rtnetlink_route_parse;
rtnetlink_notify_func *nf = (rtnetlink_notify_func *) cb;
if (!rtn) {
rtn = rtnetlink_create(RTNLGRP_IPV4_ROUTE, pf, &rtn_change);
}
return rtnetlink_notifier_register(rtn, notifier, nf, aux);
}
/* Cancels notification on 'notifier', which must have previously been
* registered with rtnetlink_route_notifier_register(). */
void
rtnetlink_route_notifier_unregister(struct rtnetlink_notifier *notifier)
{
rtnetlink_notifier_unregister(rtn, notifier);
}
/* Calls all of the registered notifiers, passing along any as-yet-unreported
* address change events. */
void
rtnetlink_route_notifier_run(void)
{
if (rtn) {
rtnetlink_notifier_run(rtn);
}
}
/* Causes poll_block() to wake up when address change notifications are ready.
*/
void
rtnetlink_route_notifier_wait(void)
{
if (rtn) {
rtnetlink_notifier_wait(rtn);
}
}

55
lib/rtnetlink-route.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2009 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef RTNETLINK_ROUTE_H
#define RTNETLINK_ROUTE_H 1
#include <stdbool.h>
#include <stdint.h>
struct ofpbuf;
struct rtnetlink_notifier;
/* A digested version of a route message sent down by the kernel to indicate
* that a route has changed. */
struct rtnetlink_route_change {
/* Copied from struct nlmsghdr. */
int nlmsg_type; /* e.g. RTM_NEWROUTE, RTM_DELROUTE. */
/* Copied from struct rtmsg. */
unsigned char rtm_dst_len;
/* Extracted from Netlink attributes. */
uint32_t rta_dst; /* Destination in host byte order. 0 if missing. */
int rta_oif; /* Output interface index. */
};
/* Function called to report that a route has changed. 'change' describes
* the specific change. It may be null, in which case the function must assume
* everything has changed. 'aux' is as specified in the call to
* rtnetlink_route_notifier_register(). */
typedef
void rtnetlink_route_notify_func(const struct rtnetlink_route_change *change,
void *aux);
bool rtnetlink_route_parse(struct ofpbuf *, struct rtnetlink_route_change *);
int rtnetlink_route_notifier_register(struct rtnetlink_notifier *,
rtnetlink_route_notify_func *, void *aux);
void rtnetlink_route_notifier_unregister(struct rtnetlink_notifier *);
void rtnetlink_route_notifier_run(void);
void rtnetlink_route_notifier_wait(void);
#endif /* rtnetlink-route.h */

View File

@@ -1101,6 +1101,14 @@ dpid_from_hash(const void *data, size_t n)
return eth_addr_to_uint64(hash);
}
static void
iface_refresh_tunnel_egress(struct iface *iface)
{
const char *name = netdev_get_tnl_iface(iface->netdev);
ovsrec_interface_set_tunnel_egress_iface(iface->cfg, name);
}
static void
iface_refresh_cfm_stats(struct iface *iface)
{
@@ -1310,6 +1318,7 @@ bridge_run(void)
struct iface *iface = port->ifaces[j];
iface_refresh_stats(iface);
iface_refresh_cfm_stats(iface);
iface_refresh_tunnel_egress(iface);
}
}
}

View File

@@ -1,6 +1,6 @@
{"name": "Open_vSwitch",
"version": "1.0.0",
"cksum": "514853437 13985",
"version": "1.0.1",
"cksum": "665434435 14130",
"tables": {
"Open_vSwitch": {
"columns": {
@@ -147,6 +147,10 @@
"ingress_policing_burst": {
"type": {"key": {"type": "integer",
"minInteger": 0}}},
"tunnel_egress_iface": {
"type": {"key": {"type": "string"},
"min": 0, "max": 1},
"ephemeral": true},
"mac": {
"type": {"key": {"type": "string"},
"min": 0, "max": 1}},

View File

@@ -1102,6 +1102,14 @@
</dl>
</column>
<column name="tunnel_egress_iface">
Egress interface for tunnels. Currently only relevant for GRE and
CAPWAP tunnels. On Linux systems, this column will show the name of
the interface which is responsible for routing traffic destined for the
configured <code>remote_ip</code>. This could be an internal interface
such as a bridge port.
</column>
<column name="other_config">
Key-value pairs for rarely used interface features. Currently,
there are none defined.