9da88ee2ad
This new OpenFlow message format provides a cleaner interface and greater detail and control over ports. It is now possible to see what features the switch's port is currently configured as having, what it's advertising, and what it's capable of handling. It is also possible to return the features advertised by the port's peer.
923 lines
28 KiB
C
923 lines
28 KiB
C
/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
|
|
* Junior University
|
|
*
|
|
* We are making the OpenFlow specification and associated documentation
|
|
* (Software) available for public use and benefit with the expectation
|
|
* that others will use, modify and enhance the Software and contribute
|
|
* those enhancements back to the community. However, since we would
|
|
* like to make the Software available for broadest use, with as few
|
|
* restrictions as possible permission is hereby granted, free of
|
|
* charge, to any person obtaining a copy of this Software to deal in
|
|
* the Software under the copyrights without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
* The name and trademarks of copyright holder(s) may NOT be used in
|
|
* advertising or publicity pertaining to the Software or any
|
|
* derivatives without specific, written prior permission.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "netdev.h"
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
#include <inttypes.h>
|
|
#include <linux/types.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/sockios.h>
|
|
#include <linux/version.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <netpacket/packet.h>
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_packet.h>
|
|
#include <net/route.h>
|
|
#include <netinet/in.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fatal-signal.h"
|
|
#include "list.h"
|
|
#include "ofpbuf.h"
|
|
#include "openflow.h"
|
|
#include "packets.h"
|
|
#include "poll-loop.h"
|
|
#include "socket-util.h"
|
|
|
|
#define THIS_MODULE VLM_netdev
|
|
#include "vlog.h"
|
|
|
|
struct netdev {
|
|
struct list node;
|
|
char *name;
|
|
int ifindex;
|
|
int fd;
|
|
uint8_t etheraddr[ETH_ADDR_LEN];
|
|
int speed;
|
|
int mtu;
|
|
|
|
/* Bitmaps of OFPPF_* that describe features. All bits disabled if
|
|
* unsupported or unavailable. */
|
|
uint32_t curr; /* Current features. */
|
|
uint32_t advertised; /* Features being advertised by the port. */
|
|
uint32_t supported; /* Features supported by the port. */
|
|
uint32_t peer; /* Features advertised by the peer. */
|
|
|
|
struct in6_addr in6;
|
|
int save_flags; /* Initial device flags. */
|
|
int changed_flags; /* Flags that we changed. */
|
|
};
|
|
|
|
/* All open network devices. */
|
|
static struct list netdev_list = LIST_INITIALIZER(&netdev_list);
|
|
|
|
/* An AF_INET socket (used for ioctl operations). */
|
|
static int af_inet_sock = -1;
|
|
|
|
/* This is set pretty low because we probably won't learn anything from the
|
|
* additional log messages. */
|
|
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
|
|
|
|
static void init_netdev(void);
|
|
static int restore_flags(struct netdev *netdev);
|
|
static int get_flags(const struct netdev *, int *flagsp);
|
|
static int set_flags(struct netdev *, int flags);
|
|
|
|
/* Obtains the IPv6 address for 'name' into 'in6'. */
|
|
static void
|
|
get_ipv6_address(const char *name, struct in6_addr *in6)
|
|
{
|
|
FILE *file;
|
|
char line[128];
|
|
|
|
file = fopen("/proc/net/if_inet6", "r");
|
|
if (file == NULL) {
|
|
/* This most likely indicates that the host doesn't have IPv6 support,
|
|
* so it's not really a failure condition.*/
|
|
*in6 = in6addr_any;
|
|
return;
|
|
}
|
|
|
|
while (fgets(line, sizeof line, file)) {
|
|
uint8_t *s6 = in6->s6_addr;
|
|
char ifname[16 + 1];
|
|
|
|
#define X8 "%2"SCNx8
|
|
if (sscanf(line, " "X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8
|
|
"%*x %*x %*x %*x %16s\n",
|
|
&s6[0], &s6[1], &s6[2], &s6[3],
|
|
&s6[4], &s6[5], &s6[6], &s6[7],
|
|
&s6[8], &s6[9], &s6[10], &s6[11],
|
|
&s6[12], &s6[13], &s6[14], &s6[15],
|
|
ifname) == 17
|
|
&& !strcmp(name, ifname))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
*in6 = in6addr_any;
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
static void
|
|
do_ethtool(struct netdev *netdev)
|
|
{
|
|
struct ifreq ifr;
|
|
struct ethtool_cmd ecmd;
|
|
|
|
netdev->curr = 0;
|
|
netdev->supported = 0;
|
|
netdev->advertised = 0;
|
|
netdev->peer = 0;
|
|
|
|
memset(&ifr, 0, sizeof ifr);
|
|
strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
|
|
ifr.ifr_data = (caddr_t) &ecmd;
|
|
|
|
memset(&ecmd, 0, sizeof ecmd);
|
|
ecmd.cmd = ETHTOOL_GSET;
|
|
if (ioctl(netdev->fd, SIOCETHTOOL, &ifr) == 0) {
|
|
if (ecmd.supported & SUPPORTED_10baseT_Half) {
|
|
netdev->supported |= OFPPF_10MB_HD;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_10baseT_Full) {
|
|
netdev->supported |= OFPPF_10MB_FD;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_100baseT_Half) {
|
|
netdev->supported |= OFPPF_100MB_HD;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_100baseT_Full) {
|
|
netdev->supported |= OFPPF_100MB_FD;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_1000baseT_Half) {
|
|
netdev->supported |= OFPPF_1GB_HD;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_1000baseT_Full) {
|
|
netdev->supported |= OFPPF_1GB_FD;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_10000baseT_Full) {
|
|
netdev->supported |= OFPPF_10GB_FD;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_TP) {
|
|
netdev->supported |= OFPPF_COPPER;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_FIBRE) {
|
|
netdev->supported |= OFPPF_FIBER;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_Autoneg) {
|
|
netdev->supported |= OFPPF_AUTONEG;
|
|
}
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
|
|
if (ecmd.supported & SUPPORTED_Pause) {
|
|
netdev->supported |= OFPPF_PAUSE;
|
|
}
|
|
if (ecmd.supported & SUPPORTED_Asym_Pause) {
|
|
netdev->supported |= OFPPF_PAUSE_ASYM;
|
|
}
|
|
#endif /* kernel >= 2.6.14 */
|
|
|
|
/* Set the advertised features */
|
|
if (ecmd.advertising & ADVERTISED_10baseT_Half) {
|
|
netdev->advertised |= OFPPF_10MB_HD;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_10baseT_Full) {
|
|
netdev->advertised |= OFPPF_10MB_FD;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_100baseT_Half) {
|
|
netdev->advertised |= OFPPF_100MB_HD;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_100baseT_Full) {
|
|
netdev->advertised |= OFPPF_100MB_FD;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_1000baseT_Half) {
|
|
netdev->advertised |= OFPPF_1GB_HD;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_1000baseT_Full) {
|
|
netdev->advertised |= OFPPF_1GB_FD;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_10000baseT_Full) {
|
|
netdev->advertised |= OFPPF_10GB_FD;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_TP) {
|
|
netdev->advertised |= OFPPF_COPPER;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_FIBRE) {
|
|
netdev->advertised |= OFPPF_FIBER;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_Autoneg) {
|
|
netdev->advertised |= OFPPF_AUTONEG;
|
|
}
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
|
|
if (ecmd.advertising & ADVERTISED_Pause) {
|
|
netdev->advertised |= OFPPF_PAUSE;
|
|
}
|
|
if (ecmd.advertising & ADVERTISED_Asym_Pause) {
|
|
netdev->advertised |= OFPPF_PAUSE_ASYM;
|
|
}
|
|
#endif /* kernel >= 2.6.14 */
|
|
|
|
/* Set the current features */
|
|
if (ecmd.speed == SPEED_10) {
|
|
netdev->curr = (ecmd.duplex) ? OFPPF_10MB_FD : OFPPF_10MB_HD;
|
|
}
|
|
else if (ecmd.speed == SPEED_100) {
|
|
netdev->curr = (ecmd.duplex) ? OFPPF_100MB_FD : OFPPF_100MB_HD;
|
|
}
|
|
else if (ecmd.speed == SPEED_1000) {
|
|
netdev->curr = (ecmd.duplex) ? OFPPF_1GB_FD : OFPPF_1GB_HD;
|
|
}
|
|
else if (ecmd.speed == SPEED_10000) {
|
|
netdev->curr = OFPPF_10GB_FD;
|
|
}
|
|
|
|
if (ecmd.port == PORT_TP) {
|
|
netdev->curr |= OFPPF_COPPER;
|
|
}
|
|
else if (ecmd.port == PORT_FIBRE) {
|
|
netdev->curr |= OFPPF_FIBER;
|
|
}
|
|
|
|
if (ecmd.autoneg) {
|
|
netdev->curr |= OFPPF_AUTONEG;
|
|
}
|
|
} else {
|
|
VLOG_DBG("ioctl(SIOCETHTOOL) failed: %s", strerror(errno));
|
|
}
|
|
}
|
|
|
|
/* Opens the network device named 'name' (e.g. "eth0") and returns zero if
|
|
* successful, otherwise a positive errno value. On success, sets '*netdev'
|
|
* to the new network device, otherwise to null.
|
|
*
|
|
* 'ethertype' may be a 16-bit Ethernet protocol value in host byte order to
|
|
* capture frames of that type received on the device. It may also be one of
|
|
* the 'enum netdev_pseudo_ethertype' values to receive frames in one of those
|
|
* categories. */
|
|
int
|
|
netdev_open(const char *name, int ethertype, struct netdev **netdev_)
|
|
{
|
|
int fd;
|
|
struct sockaddr_ll sll;
|
|
struct ifreq ifr;
|
|
unsigned int ifindex;
|
|
uint8_t etheraddr[ETH_ADDR_LEN];
|
|
struct in6_addr in6;
|
|
int mtu;
|
|
int error;
|
|
struct netdev *netdev;
|
|
|
|
*netdev_ = NULL;
|
|
init_netdev();
|
|
|
|
/* Create raw socket. */
|
|
fd = socket(PF_PACKET, SOCK_RAW,
|
|
htons(ethertype == NETDEV_ETH_TYPE_NONE ? 0
|
|
: ethertype == NETDEV_ETH_TYPE_ANY ? ETH_P_ALL
|
|
: ethertype == NETDEV_ETH_TYPE_802_2 ? ETH_P_802_2
|
|
: ethertype));
|
|
if (fd < 0) {
|
|
return errno;
|
|
}
|
|
|
|
/* Get ethernet device index. */
|
|
strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
|
|
if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
|
|
VLOG_ERR("ioctl(SIOCGIFINDEX) on %s device failed: %s",
|
|
name, strerror(errno));
|
|
goto error;
|
|
}
|
|
ifindex = ifr.ifr_ifindex;
|
|
|
|
/* Bind to specific ethernet device. */
|
|
memset(&sll, 0, sizeof sll);
|
|
sll.sll_family = AF_PACKET;
|
|
sll.sll_ifindex = ifindex;
|
|
if (bind(fd, (struct sockaddr *) &sll, sizeof sll) < 0) {
|
|
VLOG_ERR("bind to %s failed: %s", name, strerror(errno));
|
|
goto error;
|
|
}
|
|
|
|
if (ethertype != NETDEV_ETH_TYPE_NONE) {
|
|
/* Between the socket() and bind() calls above, the socket receives all
|
|
* packets of the requested type on all system interfaces. We do not
|
|
* want to receive that data, but there is no way to avoid it. So we
|
|
* must now drain out the receive queue. */
|
|
error = drain_rcvbuf(fd);
|
|
if (error) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Get MAC address. */
|
|
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
|
|
VLOG_ERR("ioctl(SIOCGIFHWADDR) on %s device failed: %s",
|
|
name, strerror(errno));
|
|
goto error;
|
|
}
|
|
if (ifr.ifr_hwaddr.sa_family != AF_UNSPEC
|
|
&& ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
|
|
VLOG_WARN("%s device has unknown hardware address family %d",
|
|
name, (int) ifr.ifr_hwaddr.sa_family);
|
|
}
|
|
memcpy(etheraddr, ifr.ifr_hwaddr.sa_data, sizeof etheraddr);
|
|
|
|
/* Get MTU. */
|
|
if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
|
|
VLOG_ERR("ioctl(SIOCGIFMTU) on %s device failed: %s",
|
|
name, strerror(errno));
|
|
goto error;
|
|
}
|
|
mtu = ifr.ifr_mtu;
|
|
|
|
get_ipv6_address(name, &in6);
|
|
|
|
/* Allocate network device. */
|
|
netdev = xmalloc(sizeof *netdev);
|
|
netdev->name = xstrdup(name);
|
|
netdev->ifindex = ifindex;
|
|
netdev->fd = fd;
|
|
memcpy(netdev->etheraddr, etheraddr, sizeof etheraddr);
|
|
netdev->mtu = mtu;
|
|
netdev->in6 = in6;
|
|
|
|
/* Get speed, features. */
|
|
do_ethtool(netdev);
|
|
|
|
/* Save flags to restore at close or exit. */
|
|
error = get_flags(netdev, &netdev->save_flags);
|
|
if (error) {
|
|
goto preset_error;
|
|
}
|
|
netdev->changed_flags = 0;
|
|
fatal_signal_block();
|
|
list_push_back(&netdev_list, &netdev->node);
|
|
fatal_signal_unblock();
|
|
|
|
/* Success! */
|
|
*netdev_ = netdev;
|
|
return 0;
|
|
|
|
error:
|
|
error = errno;
|
|
preset_error:
|
|
close(fd);
|
|
return error;
|
|
}
|
|
|
|
/* Closes and destroys 'netdev'. */
|
|
void
|
|
netdev_close(struct netdev *netdev)
|
|
{
|
|
if (netdev) {
|
|
/* Bring down interface and drop promiscuous mode, if we brought up
|
|
* the interface or enabled promiscuous mode. */
|
|
int error;
|
|
fatal_signal_block();
|
|
error = restore_flags(netdev);
|
|
list_remove(&netdev->node);
|
|
fatal_signal_unblock();
|
|
if (error) {
|
|
VLOG_WARN("failed to restore network device flags on %s: %s",
|
|
netdev->name, strerror(error));
|
|
}
|
|
|
|
/* Free. */
|
|
free(netdev->name);
|
|
close(netdev->fd);
|
|
free(netdev);
|
|
}
|
|
}
|
|
|
|
/* Pads 'buffer' out with zero-bytes to the minimum valid length of an
|
|
* Ethernet packet, if necessary. */
|
|
static void
|
|
pad_to_minimum_length(struct ofpbuf *buffer)
|
|
{
|
|
if (buffer->size < ETH_TOTAL_MIN) {
|
|
size_t shortage = ETH_TOTAL_MIN - buffer->size;
|
|
memset(ofpbuf_put_uninit(buffer, shortage), 0, shortage);
|
|
}
|
|
}
|
|
|
|
/* Attempts to receive a packet from 'netdev' into 'buffer', which the caller
|
|
* must have initialized with sufficient room for the packet. The space
|
|
* required to receive any packet is ETH_HEADER_LEN bytes, plus VLAN_HEADER_LEN
|
|
* bytes, plus the device's MTU (which may be retrieved via netdev_get_mtu()).
|
|
* (Some devices do not allow for a VLAN header, in which case VLAN_HEADER_LEN
|
|
* need not be included.)
|
|
*
|
|
* If a packet is successfully retrieved, returns 0. In this case 'buffer' is
|
|
* guaranteed to contain at least ETH_TOTAL_MIN bytes. Otherwise, returns a
|
|
* positive errno value. Returns EAGAIN immediately if no packet is ready to
|
|
* be returned.
|
|
*/
|
|
int
|
|
netdev_recv(struct netdev *netdev, struct ofpbuf *buffer)
|
|
{
|
|
ssize_t n_bytes;
|
|
|
|
assert(buffer->size == 0);
|
|
assert(ofpbuf_tailroom(buffer) >= ETH_TOTAL_MIN);
|
|
do {
|
|
n_bytes = recv(netdev->fd,
|
|
ofpbuf_tail(buffer), ofpbuf_tailroom(buffer),
|
|
MSG_DONTWAIT);
|
|
} while (n_bytes < 0 && errno == EINTR);
|
|
if (n_bytes < 0) {
|
|
if (errno != EAGAIN) {
|
|
VLOG_WARN_RL(&rl, "error receiving Ethernet packet on %s: %s",
|
|
strerror(errno), netdev->name);
|
|
}
|
|
return errno;
|
|
} else {
|
|
buffer->size += n_bytes;
|
|
|
|
/* When the kernel internally sends out an Ethernet frame on an
|
|
* interface, it gives us a copy *before* padding the frame to the
|
|
* minimum length. Thus, when it sends out something like an ARP
|
|
* request, we see a too-short frame. So pad it out to the minimum
|
|
* length. */
|
|
pad_to_minimum_length(buffer);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Registers with the poll loop to wake up from the next call to poll_block()
|
|
* when a packet is ready to be received with netdev_recv() on 'netdev'. */
|
|
void
|
|
netdev_recv_wait(struct netdev *netdev)
|
|
{
|
|
poll_fd_wait(netdev->fd, POLLIN);
|
|
}
|
|
|
|
/* Discards all packets waiting to be received from 'netdev'. */
|
|
void
|
|
netdev_drain(struct netdev *netdev)
|
|
{
|
|
drain_rcvbuf(netdev->fd);
|
|
}
|
|
|
|
/* Sends 'buffer' on 'netdev'. Returns 0 if successful, otherwise a positive
|
|
* errno value. Returns EAGAIN without blocking if the packet cannot be queued
|
|
* immediately. Returns EMSGSIZE if a partial packet was transmitted or if
|
|
* the packet is too big or too small to transmit on the device.
|
|
*
|
|
* The caller retains ownership of 'buffer' in all cases.
|
|
*
|
|
* The kernel maintains a packet transmission queue, so the caller is not
|
|
* expected to do additional queuing of packets. */
|
|
int
|
|
netdev_send(struct netdev *netdev, const struct ofpbuf *buffer)
|
|
{
|
|
ssize_t n_bytes;
|
|
const struct eth_header *eh;
|
|
|
|
/* Pull out the Ethernet header. */
|
|
if (buffer->size < ETH_HEADER_LEN) {
|
|
VLOG_WARN_RL(&rl, "cannot send %zu-byte frame on %s",
|
|
buffer->size, netdev->name);
|
|
return EMSGSIZE;
|
|
}
|
|
eh = ofpbuf_at_assert(buffer, 0, sizeof *eh);
|
|
|
|
do {
|
|
n_bytes = sendto(netdev->fd, buffer->data, buffer->size, 0, NULL, 0);
|
|
} while (n_bytes < 0 && errno == EINTR);
|
|
|
|
if (n_bytes < 0) {
|
|
/* The Linux AF_PACKET implementation never blocks waiting for room
|
|
* for packets, instead returning ENOBUFS. Translate this into EAGAIN
|
|
* for the caller. */
|
|
if (errno == ENOBUFS) {
|
|
return EAGAIN;
|
|
} else if (errno != EAGAIN) {
|
|
VLOG_WARN_RL(&rl, "error sending Ethernet packet on %s: %s",
|
|
netdev->name, strerror(errno));
|
|
}
|
|
return errno;
|
|
} else if (n_bytes != buffer->size) {
|
|
VLOG_WARN_RL(&rl,
|
|
"send partial Ethernet packet (%d bytes of %zu) on %s",
|
|
(int) n_bytes, buffer->size, netdev->name);
|
|
return EMSGSIZE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Registers with the poll loop to wake up from the next call to poll_block()
|
|
* when the packet transmission queue has sufficient room to transmit a packet
|
|
* with netdev_send().
|
|
*
|
|
* The kernel maintains a packet transmission queue, so the client is not
|
|
* expected to do additional queuing of packets. Thus, this function is
|
|
* unlikely to ever be used. It is included for completeness. */
|
|
void
|
|
netdev_send_wait(struct netdev *netdev)
|
|
{
|
|
poll_fd_wait(netdev->fd, POLLOUT);
|
|
}
|
|
|
|
/* Returns a pointer to 'netdev''s MAC address. The caller must not modify or
|
|
* free the returned buffer. */
|
|
const uint8_t *
|
|
netdev_get_etheraddr(const struct netdev *netdev)
|
|
{
|
|
return netdev->etheraddr;
|
|
}
|
|
|
|
/* Returns the name of the network device that 'netdev' represents,
|
|
* e.g. "eth0". The caller must not modify or free the returned string. */
|
|
const char *
|
|
netdev_get_name(const struct netdev *netdev)
|
|
{
|
|
return netdev->name;
|
|
}
|
|
|
|
/* Returns the maximum size of transmitted (and received) packets on 'netdev',
|
|
* in bytes, not including the hardware header; thus, this is typically 1500
|
|
* bytes for Ethernet devices. */
|
|
int
|
|
netdev_get_mtu(const struct netdev *netdev)
|
|
{
|
|
return netdev->mtu;
|
|
}
|
|
|
|
/* Checks the link status. Returns 1 or 0 to indicate the link is active
|
|
* or not, respectively. Any other return value indicates an error. */
|
|
int
|
|
netdev_get_link_status(const struct netdev *netdev)
|
|
{
|
|
struct ifreq ifr;
|
|
struct ethtool_value edata;
|
|
|
|
memset(&ifr, 0, sizeof ifr);
|
|
strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
|
|
ifr.ifr_data = (caddr_t) &edata;
|
|
|
|
memset(&edata, 0, sizeof edata);
|
|
edata.cmd = ETHTOOL_GLINK;
|
|
if (ioctl(netdev->fd, SIOCETHTOOL, &ifr) == 0) {
|
|
if (edata.data) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Returns the features supported by 'netdev' of type 'type', as a bitmap
|
|
* of bits from enum ofp_phy_features, in host byte order. */
|
|
uint32_t
|
|
netdev_get_features(struct netdev *netdev, int type)
|
|
{
|
|
do_ethtool(netdev);
|
|
switch (type) {
|
|
case NETDEV_FEAT_CURRENT:
|
|
return netdev->curr;
|
|
case NETDEV_FEAT_ADVERTISED:
|
|
return netdev->advertised;
|
|
case NETDEV_FEAT_SUPPORTED:
|
|
return netdev->supported;
|
|
case NETDEV_FEAT_PEER:
|
|
return netdev->peer;
|
|
default:
|
|
VLOG_WARN("Unknown feature type: %d\n", type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* If 'netdev' has an assigned IPv4 address, sets '*in4' to that address (if
|
|
* 'in4' is non-null) and returns true. Otherwise, returns false. */
|
|
bool
|
|
netdev_get_in4(const struct netdev *netdev, struct in_addr *in4)
|
|
{
|
|
struct ifreq ifr;
|
|
struct in_addr ip = { INADDR_ANY };
|
|
|
|
strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
|
|
ifr.ifr_addr.sa_family = AF_INET;
|
|
if (ioctl(af_inet_sock, SIOCGIFADDR, &ifr) == 0) {
|
|
struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
|
|
ip = sin->sin_addr;
|
|
} else {
|
|
VLOG_DBG_RL(&rl, "%s: ioctl(SIOCGIFADDR) failed: %s",
|
|
netdev->name, strerror(errno));
|
|
}
|
|
if (in4) {
|
|
*in4 = ip;
|
|
}
|
|
return ip.s_addr != INADDR_ANY;
|
|
}
|
|
|
|
static void
|
|
make_in4_sockaddr(struct sockaddr *sa, struct in_addr addr)
|
|
{
|
|
struct sockaddr_in sin;
|
|
memset(&sin, 0, sizeof sin);
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_addr = addr;
|
|
sin.sin_port = 0;
|
|
|
|
memset(sa, 0, sizeof *sa);
|
|
memcpy(sa, &sin, sizeof sin);
|
|
}
|
|
|
|
static int
|
|
do_set_addr(struct netdev *netdev, int sock,
|
|
int ioctl_nr, const char *ioctl_name, struct in_addr addr)
|
|
{
|
|
struct ifreq ifr;
|
|
int error;
|
|
|
|
strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
|
|
make_in4_sockaddr(&ifr.ifr_addr, addr);
|
|
error = ioctl(sock, ioctl_nr, &ifr) < 0 ? errno : 0;
|
|
if (error) {
|
|
VLOG_WARN("ioctl(%s): %s", ioctl_name, strerror(error));
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/* Assigns 'addr' as 'netdev''s IPv4 address and 'mask' as its netmask. If
|
|
* 'addr' is INADDR_ANY, 'netdev''s IPv4 address is cleared. Returns a
|
|
* positive errno value. */
|
|
int
|
|
netdev_set_in4(struct netdev *netdev, struct in_addr addr, struct in_addr mask)
|
|
{
|
|
int error;
|
|
|
|
error = do_set_addr(netdev, af_inet_sock,
|
|
SIOCSIFADDR, "SIOCSIFADDR", addr);
|
|
if (!error && addr.s_addr != INADDR_ANY) {
|
|
error = do_set_addr(netdev, af_inet_sock,
|
|
SIOCSIFNETMASK, "SIOCSIFNETMASK", mask);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/* Adds 'router' as a default gateway for 'netdev''s IP address. */
|
|
int
|
|
netdev_add_router(struct netdev *netdev, struct in_addr router)
|
|
{
|
|
struct in_addr any = { INADDR_ANY };
|
|
struct rtentry rt;
|
|
int error;
|
|
|
|
memset(&rt, 0, sizeof rt);
|
|
make_in4_sockaddr(&rt.rt_dst, any);
|
|
make_in4_sockaddr(&rt.rt_gateway, router);
|
|
make_in4_sockaddr(&rt.rt_genmask, any);
|
|
rt.rt_flags = RTF_UP | RTF_GATEWAY;
|
|
error = ioctl(af_inet_sock, SIOCADDRT, &rt) < 0 ? errno : 0;
|
|
if (error) {
|
|
VLOG_WARN("ioctl(SIOCADDRT): %s", strerror(error));
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address (if
|
|
* 'in6' is non-null) and returns true. Otherwise, returns false. */
|
|
bool
|
|
netdev_get_in6(const struct netdev *netdev, struct in6_addr *in6)
|
|
{
|
|
if (in6) {
|
|
*in6 = netdev->in6;
|
|
}
|
|
return memcmp(&netdev->in6, &in6addr_any, sizeof netdev->in6) != 0;
|
|
}
|
|
|
|
/* Obtains the current flags for 'netdev' and stores them into '*flagsp'.
|
|
* Returns 0 if successful, otherwise a positive errno value. */
|
|
int
|
|
netdev_get_flags(const struct netdev *netdev, enum netdev_flags *flagsp)
|
|
{
|
|
int error, flags;
|
|
|
|
error = get_flags(netdev, &flags);
|
|
if (error) {
|
|
return error;
|
|
}
|
|
|
|
*flagsp = 0;
|
|
if (flags & IFF_UP) {
|
|
*flagsp |= NETDEV_UP;
|
|
}
|
|
if (flags & IFF_PROMISC) {
|
|
*flagsp |= NETDEV_PROMISC;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
nd_to_iff_flags(enum netdev_flags nd)
|
|
{
|
|
int iff = 0;
|
|
if (nd & NETDEV_UP) {
|
|
iff |= IFF_UP;
|
|
}
|
|
if (nd & NETDEV_PROMISC) {
|
|
iff |= IFF_PROMISC;
|
|
}
|
|
return iff;
|
|
}
|
|
|
|
/* On 'netdev', turns off the flags in 'off' and then turns on the flags in
|
|
* 'on'. If 'permanent' is true, the changes will persist; otherwise, they
|
|
* will be reverted when 'netdev' is closed or the program exits. Returns 0 if
|
|
* successful, otherwise a positive errno value. */
|
|
static int
|
|
do_update_flags(struct netdev *netdev, enum netdev_flags off,
|
|
enum netdev_flags on, bool permanent)
|
|
{
|
|
int old_flags, new_flags;
|
|
int error;
|
|
|
|
error = get_flags(netdev, &old_flags);
|
|
if (error) {
|
|
return error;
|
|
}
|
|
|
|
new_flags = (old_flags & ~nd_to_iff_flags(off)) | nd_to_iff_flags(on);
|
|
if (!permanent) {
|
|
netdev->changed_flags |= new_flags ^ old_flags;
|
|
}
|
|
if (new_flags != old_flags) {
|
|
error = set_flags(netdev, new_flags);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/* Sets the flags for 'netdev' to 'flags'.
|
|
* If 'permanent' is true, the changes will persist; otherwise, they
|
|
* will be reverted when 'netdev' is closed or the program exits.
|
|
* Returns 0 if successful, otherwise a positive errno value. */
|
|
int
|
|
netdev_set_flags(struct netdev *netdev, enum netdev_flags flags,
|
|
bool permanent)
|
|
{
|
|
return do_update_flags(netdev, -1, flags, permanent);
|
|
}
|
|
|
|
/* Turns on the specified 'flags' on 'netdev'.
|
|
* If 'permanent' is true, the changes will persist; otherwise, they
|
|
* will be reverted when 'netdev' is closed or the program exits.
|
|
* Returns 0 if successful, otherwise a positive errno value. */
|
|
int
|
|
netdev_turn_flags_on(struct netdev *netdev, enum netdev_flags flags,
|
|
bool permanent)
|
|
{
|
|
return do_update_flags(netdev, 0, flags, permanent);
|
|
}
|
|
|
|
/* Turns off the specified 'flags' on 'netdev'.
|
|
* If 'permanent' is true, the changes will persist; otherwise, they
|
|
* will be reverted when 'netdev' is closed or the program exits.
|
|
* Returns 0 if successful, otherwise a positive errno value. */
|
|
int
|
|
netdev_turn_flags_off(struct netdev *netdev, enum netdev_flags flags,
|
|
bool permanent)
|
|
{
|
|
return do_update_flags(netdev, flags, 0, permanent);
|
|
}
|
|
|
|
/* Looks up the ARP table entry for 'ip' on 'netdev'. If one exists and can be
|
|
* successfully retrieved, it stores the corresponding MAC address in 'mac' and
|
|
* returns 0. Otherwise, it returns a positive errno value; in particular,
|
|
* ENXIO indicates that there is not ARP table entry for 'ip' on 'netdev'. */
|
|
int
|
|
netdev_arp_lookup(const struct netdev *netdev,
|
|
uint32_t ip, uint8_t mac[ETH_ADDR_LEN])
|
|
{
|
|
struct arpreq r;
|
|
struct sockaddr_in *pa;
|
|
int retval;
|
|
|
|
memset(&r, 0, sizeof r);
|
|
pa = (struct sockaddr_in *) &r.arp_pa;
|
|
pa->sin_family = AF_INET;
|
|
pa->sin_addr.s_addr = ip;
|
|
pa->sin_port = 0;
|
|
r.arp_ha.sa_family = ARPHRD_ETHER;
|
|
r.arp_flags = 0;
|
|
strncpy(r.arp_dev, netdev->name, sizeof r.arp_dev);
|
|
retval = ioctl(af_inet_sock, SIOCGARP, &r) < 0 ? errno : 0;
|
|
if (!retval) {
|
|
memcpy(mac, r.arp_ha.sa_data, ETH_ADDR_LEN);
|
|
} else if (retval != ENXIO) {
|
|
VLOG_WARN_RL(&rl, "%s: could not look up ARP entry for "IP_FMT": %s",
|
|
netdev->name, IP_ARGS(&ip), strerror(retval));
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static void restore_all_flags(void *aux);
|
|
|
|
/* Set up a signal hook to restore network device flags on program
|
|
* termination. */
|
|
static void
|
|
init_netdev(void)
|
|
{
|
|
static bool inited;
|
|
if (!inited) {
|
|
inited = true;
|
|
fatal_signal_add_hook(restore_all_flags, NULL, true);
|
|
af_inet_sock = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (af_inet_sock < 0) {
|
|
ofp_fatal(errno, "socket(AF_INET)");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Restore the network device flags on 'netdev' to those that were active
|
|
* before we changed them. Returns 0 if successful, otherwise a positive
|
|
* errno value.
|
|
*
|
|
* To avoid reentry, the caller must ensure that fatal signals are blocked. */
|
|
static int
|
|
restore_flags(struct netdev *netdev)
|
|
{
|
|
struct ifreq ifr;
|
|
int restore_flags;
|
|
|
|
/* Get current flags. */
|
|
strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
|
|
if (ioctl(netdev->fd, SIOCGIFFLAGS, &ifr) < 0) {
|
|
return errno;
|
|
}
|
|
|
|
/* Restore flags that we might have changed, if necessary. */
|
|
restore_flags = netdev->changed_flags & (IFF_PROMISC | IFF_UP);
|
|
if ((ifr.ifr_flags ^ netdev->save_flags) & restore_flags) {
|
|
ifr.ifr_flags &= ~restore_flags;
|
|
ifr.ifr_flags |= netdev->save_flags & restore_flags;
|
|
if (ioctl(netdev->fd, SIOCSIFFLAGS, &ifr) < 0) {
|
|
return errno;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Retores all the flags on all network devices that we modified. Called from
|
|
* a signal handler, so it does not attempt to report error conditions. */
|
|
static void
|
|
restore_all_flags(void *aux UNUSED)
|
|
{
|
|
struct netdev *netdev;
|
|
LIST_FOR_EACH (netdev, struct netdev, node, &netdev_list) {
|
|
restore_flags(netdev);
|
|
}
|
|
}
|
|
|
|
static int
|
|
get_flags(const struct netdev *netdev, int *flags)
|
|
{
|
|
struct ifreq ifr;
|
|
strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
|
|
if (ioctl(netdev->fd, SIOCGIFFLAGS, &ifr) < 0) {
|
|
VLOG_ERR("ioctl(SIOCGIFFLAGS) on %s device failed: %s",
|
|
netdev->name, strerror(errno));
|
|
return errno;
|
|
}
|
|
*flags = ifr.ifr_flags;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
set_flags(struct netdev *netdev, int flags)
|
|
{
|
|
struct ifreq ifr;
|
|
strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
|
|
ifr.ifr_flags = flags;
|
|
if (ioctl(netdev->fd, SIOCSIFFLAGS, &ifr) < 0) {
|
|
VLOG_ERR("ioctl(SIOCSIFFLAGS) on %s device failed: %s",
|
|
netdev->name, strerror(errno));
|
|
return errno;
|
|
}
|
|
return 0;
|
|
}
|