/*
- * Copyright (c) 2014 VMware, Inc.
+ * Copyright (c) 2014, 2016 VMware, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <stdlib.h>
#include <config.h>
#include <errno.h>
+#include <iphlpapi.h>
#include <net/if.h>
#include "fatal-signal.h"
#include "netdev-provider.h"
#include "ofpbuf.h"
+#include "packets.h"
#include "poll-loop.h"
#include "shash.h"
#include "svec.h"
-#include "vlog.h"
+#include "openvswitch/vlog.h"
#include "odp-netlink.h"
#include "netlink-socket.h"
#include "netlink.h"
unsigned int cache_valid;
int ifindex;
- uint8_t mac[ETH_ADDR_LEN];
+ struct eth_addr mac;
uint32_t mtu;
unsigned int ifi_flags;
};
/* General information of a network device. */
const char *name;
- uint8_t mac_address[ETH_ADDR_LEN];
+ struct eth_addr mac_address;
uint32_t mtu;
uint32_t ifi_flags;
};
+static int query_netdev(const char *devname,
+ struct netdev_windows_netdev_info *reply,
+ struct ofpbuf **bufp);
+static struct netdev *netdev_windows_alloc(void);
+static int netdev_windows_init_(void);
+
+/* Generic Netlink family numbers for OVS.
+ *
+ * Initialized by netdev_windows_init_(). */
+static int ovs_win_netdev_family;
+struct nl_sock *ovs_win_netdev_sock;
+
+
+static bool
+is_netdev_windows_class(const struct netdev_class *netdev_class)
+{
+ return netdev_class->alloc == netdev_windows_alloc;
+}
+
+static struct netdev_windows *
+netdev_windows_cast(const struct netdev *netdev_)
+{
+ ovs_assert(is_netdev_windows_class(netdev_get_class(netdev_)));
+ return CONTAINER_OF(netdev_, struct netdev_windows, up);
+}
+
static int
-netdev_windows_init(void)
+netdev_windows_init_(void)
{
- return EINVAL;
+ int error = 0;
+ static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
+
+ if (ovsthread_once_start(&once)) {
+ error = nl_lookup_genl_family(OVS_WIN_NETDEV_FAMILY,
+ &ovs_win_netdev_family);
+ if (error) {
+ VLOG_ERR("Generic Netlink family '%s' does not exist. "
+ "The Open vSwitch kernel module is probably not loaded.",
+ OVS_WIN_NETDEV_FAMILY);
+ }
+ if (!error) {
+ /* XXX: Where to close this socket? */
+ error = nl_sock_create(NETLINK_GENERIC, &ovs_win_netdev_sock);
+ }
+
+ ovsthread_once_done(&once);
+ }
+
+ return error;
}
static struct netdev *
netdev_windows_alloc(void)
{
- return NULL;
+ struct netdev_windows *netdev = xzalloc(sizeof *netdev);
+ return netdev ? &netdev->up : NULL;
+}
+
+static uint32_t
+dp_to_netdev_ifi_flags(uint32_t dp_flags)
+{
+ uint32_t nd_flags = 0;
+
+ if (dp_flags && OVS_WIN_NETDEV_IFF_UP) {
+ nd_flags |= NETDEV_UP;
+ }
+
+ if (dp_flags && OVS_WIN_NETDEV_IFF_PROMISC) {
+ nd_flags |= NETDEV_PROMISC;
+ }
+
+ return nd_flags;
}
static int
netdev_windows_system_construct(struct netdev *netdev_)
{
- return EINVAL;
+ struct netdev_windows *netdev = netdev_windows_cast(netdev_);
+ struct netdev_windows_netdev_info info;
+ struct ofpbuf *buf;
+ int ret;
+
+ /* Query the attributes and runtime status of the netdev. */
+ ret = query_netdev(netdev_get_name(&netdev->up), &info, &buf);
+ if (ret) {
+ return ret;
+ }
+ ofpbuf_delete(buf);
+
+ netdev->change_seq = 1;
+ netdev->dev_type = info.ovs_type;
+ netdev->port_no = info.port_no;
+
+ netdev->mac = info.mac_address;
+ netdev->cache_valid = VALID_ETHERADDR;
+ netdev->ifindex = -EOPNOTSUPP;
+
+ netdev->mtu = info.mtu;
+ netdev->cache_valid |= VALID_MTU;
+
+ netdev->ifi_flags = dp_to_netdev_ifi_flags(info.ifi_flags);
+ netdev->cache_valid |= VALID_IFFLAG;
+
+ VLOG_DBG("construct device %s, ovs_type: %u.",
+ netdev_get_name(&netdev->up), info.ovs_type);
+ return 0;
+}
+
+static int
+netdev_windows_netdev_to_ofpbuf(struct netdev_windows_netdev_info *info,
+ struct ofpbuf *buf)
+{
+ struct ovs_header *ovs_header;
+ int error = EINVAL;
+
+ nl_msg_put_genlmsghdr(buf, 0, ovs_win_netdev_family,
+ NLM_F_REQUEST | NLM_F_ECHO,
+ info->cmd, OVS_WIN_NETDEV_VERSION);
+
+ ovs_header = ofpbuf_put_uninit(buf, sizeof *ovs_header);
+ ovs_header->dp_ifindex = info->dp_ifindex;
+
+ if (info->name) {
+ nl_msg_put_string(buf, OVS_WIN_NETDEV_ATTR_NAME, info->name);
+ error = 0;
+ }
+
+ return error;
+}
+
+static void
+netdev_windows_info_init(struct netdev_windows_netdev_info *info)
+{
+ memset(info, 0, sizeof *info);
+}
+
+static int
+netdev_windows_netdev_from_ofpbuf(struct netdev_windows_netdev_info *info,
+ struct ofpbuf *buf)
+{
+ static const struct nl_policy ovs_netdev_policy[] = {
+ [OVS_WIN_NETDEV_ATTR_PORT_NO] = { .type = NL_A_U32 },
+ [OVS_WIN_NETDEV_ATTR_TYPE] = { .type = NL_A_U32 },
+ [OVS_WIN_NETDEV_ATTR_NAME] = { .type = NL_A_STRING, .max_len = IFNAMSIZ },
+ [OVS_WIN_NETDEV_ATTR_MAC_ADDR] = { NL_POLICY_FOR(info->mac_address) },
+ [OVS_WIN_NETDEV_ATTR_MTU] = { .type = NL_A_U32 },
+ [OVS_WIN_NETDEV_ATTR_IF_FLAGS] = { .type = NL_A_U32 },
+ };
+
+ netdev_windows_info_init(info);
+
+ struct ofpbuf b = ofpbuf_const_initializer(&b, buf->data, buf->size);
+ struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
+ struct genlmsghdr *genl = ofpbuf_try_pull(&b, sizeof *genl);
+ struct ovs_header *ovs_header = ofpbuf_try_pull(&b, sizeof *ovs_header);
+
+ struct nlattr *a[ARRAY_SIZE(ovs_netdev_policy)];
+ if (!nlmsg || !genl || !ovs_header
+ || nlmsg->nlmsg_type != ovs_win_netdev_family
+ || !nl_policy_parse(&b, 0, ovs_netdev_policy, a,
+ ARRAY_SIZE(ovs_netdev_policy))) {
+ return EINVAL;
+ }
+
+ info->cmd = genl->cmd;
+ info->dp_ifindex = ovs_header->dp_ifindex;
+ info->port_no = nl_attr_get_odp_port(a[OVS_WIN_NETDEV_ATTR_PORT_NO]);
+ info->ovs_type = nl_attr_get_u32(a[OVS_WIN_NETDEV_ATTR_TYPE]);
+ info->name = nl_attr_get_string(a[OVS_WIN_NETDEV_ATTR_NAME]);
+ memcpy(&info->mac_address, nl_attr_get_unspec(a[OVS_WIN_NETDEV_ATTR_MAC_ADDR],
+ sizeof(info->mac_address)), sizeof(info->mac_address));
+ info->mtu = nl_attr_get_u32(a[OVS_WIN_NETDEV_ATTR_MTU]);
+ info->ifi_flags = nl_attr_get_u32(a[OVS_WIN_NETDEV_ATTR_IF_FLAGS]);
+
+ return 0;
+}
+
+static int
+query_netdev(const char *devname,
+ struct netdev_windows_netdev_info *info,
+ struct ofpbuf **bufp)
+{
+ int error = 0;
+ struct ofpbuf *request_buf;
+
+ ovs_assert(info != NULL);
+ netdev_windows_info_init(info);
+
+ error = netdev_windows_init_();
+ if (error) {
+ if (info) {
+ *bufp = NULL;
+ netdev_windows_info_init(info);
+ }
+ return error;
+ }
+
+ request_buf = ofpbuf_new(1024);
+ info->cmd = OVS_WIN_NETDEV_CMD_GET;
+ info->name = devname;
+ error = netdev_windows_netdev_to_ofpbuf(info, request_buf);
+ if (error) {
+ ofpbuf_delete(request_buf);
+ return error;
+ }
+
+ error = nl_transact(NETLINK_GENERIC, request_buf, bufp);
+ ofpbuf_delete(request_buf);
+
+ if (info) {
+ if (!error) {
+ error = netdev_windows_netdev_from_ofpbuf(info, *bufp);
+ }
+ if (error) {
+ netdev_windows_info_init(info);
+ ofpbuf_delete(*bufp);
+ *bufp = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static void
+netdev_windows_destruct(struct netdev *netdev_)
+{
+
}
static void
netdev_windows_dealloc(struct netdev *netdev_)
{
+ struct netdev_windows *netdev = netdev_windows_cast(netdev_);
+ free(netdev);
}
static int
-netdev_windows_get_etheraddr(const struct netdev *netdev_, uint8_t mac[6])
+netdev_windows_get_etheraddr(const struct netdev *netdev_,
+ struct eth_addr *mac)
{
- return EINVAL;
+ struct netdev_windows *netdev = netdev_windows_cast(netdev_);
+
+ ovs_assert((netdev->cache_valid & VALID_ETHERADDR) != 0);
+ if (netdev->cache_valid & VALID_ETHERADDR) {
+ *mac = netdev->mac;
+ } else {
+ return EINVAL;
+ }
+ return 0;
}
static int
netdev_windows_get_mtu(const struct netdev *netdev_, int *mtup)
{
- return EINVAL;
+ struct netdev_windows *netdev = netdev_windows_cast(netdev_);
+
+ ovs_assert((netdev->cache_valid & VALID_MTU) != 0);
+ if (netdev->cache_valid & VALID_MTU) {
+ *mtup = netdev->mtu;
+ } else {
+ return EINVAL;
+ }
+ return 0;
+}
+
+/* This functionality is not really required by the datapath.
+ * But vswitchd bringup expects this to be implemented. */
+static int
+netdev_windows_set_etheraddr(const struct netdev *netdev_,
+ const struct eth_addr mac)
+{
+ return 0;
+}
+
+/* This functionality is not really required by the datapath.
+ * But vswitchd bringup expects this to be implemented. */
+static int
+netdev_windows_update_flags(struct netdev *netdev_,
+ enum netdev_flags off,
+ enum netdev_flags on,
+ enum netdev_flags *old_flagsp)
+{
+ struct netdev_windows *netdev = netdev_windows_cast(netdev_);
+
+ ovs_assert((netdev->cache_valid & VALID_IFFLAG) != 0);
+ if (netdev->cache_valid & VALID_IFFLAG) {
+ *old_flagsp = netdev->ifi_flags;
+ /* Setting the interface flags is not supported. */
+ } else {
+ return EINVAL;
+ }
+ return 0;
+}
+
+/* Looks up in the ARP table entry for a given 'ip'. If it is found, the
+ * corresponding MAC address will be copied in 'mac' and return 0. If no
+ * matching entry is found or an error occurs it will log it and return ENXIO.
+ */
+static int
+netdev_windows_arp_lookup(const struct netdev *netdev,
+ ovs_be32 ip, struct eth_addr *mac)
+{
+ PMIB_IPNETTABLE arp_table = NULL;
+ /* The buffer length of all ARP entries */
+ uint32_t buffer_length = 0;
+ uint32_t ret_val = 0;
+ uint32_t counter = 0;
+
+ ret_val = GetIpNetTable(arp_table, &buffer_length, false);
+
+ if (ret_val != ERROR_INSUFFICIENT_BUFFER ) {
+ VLOG_ERR("Call to GetIpNetTable failed with error: %s",
+ ovs_format_message(ret_val));
+ return ENXIO;
+ }
+
+ arp_table = (MIB_IPNETTABLE *) malloc(buffer_length);
+
+ if (arp_table == NULL) {
+ VLOG_ERR("Could not allocate memory for all the interfaces");
+ return ENXIO;
+ }
+
+ ret_val = GetIpNetTable(arp_table, &buffer_length, false);
+
+ if (ret_val == NO_ERROR) {
+ for (counter = 0; counter < arp_table->dwNumEntries; counter++) {
+ if (arp_table->table[counter].dwAddr == ip) {
+ memcpy(mac, arp_table->table[counter].bPhysAddr, ETH_ADDR_LEN);
+
+ free(arp_table);
+ return 0;
+ }
+ }
+ } else {
+ VLOG_ERR("Call to GetIpNetTable failed with error: %s",
+ ovs_format_message(ret_val));
+ }
+
+ free(arp_table);
+ return ENXIO;
+}
+
+static int
+netdev_windows_get_next_hop(const struct in_addr *host,
+ struct in_addr *next_hop,
+ char **netdev_name)
+{
+ uint32_t ret_val = 0;
+ /* The buffer length of all addresses */
+ uint32_t buffer_length = 1000;
+ PIP_ADAPTER_ADDRESSES all_addr = NULL;
+ PIP_ADAPTER_ADDRESSES cur_addr = NULL;
+
+ ret_val = GetAdaptersAddresses(AF_INET,
+ GAA_FLAG_INCLUDE_PREFIX |
+ GAA_FLAG_INCLUDE_GATEWAYS,
+ NULL, all_addr, &buffer_length);
+
+ if (ret_val != ERROR_INSUFFICIENT_BUFFER ) {
+ VLOG_ERR("Call to GetAdaptersAddresses failed with error: %s",
+ ovs_format_message(ret_val));
+ return ENXIO;
+ }
+
+ all_addr = (IP_ADAPTER_ADDRESSES *) malloc(buffer_length);
+
+ if (all_addr == NULL) {
+ VLOG_ERR("Could not allocate memory for all the interfaces");
+ return ENXIO;
+ }
+
+ ret_val = GetAdaptersAddresses(AF_INET,
+ GAA_FLAG_INCLUDE_PREFIX |
+ GAA_FLAG_INCLUDE_GATEWAYS,
+ NULL, all_addr, &buffer_length);
+
+ if (ret_val == NO_ERROR) {
+ cur_addr = all_addr;
+ while (cur_addr) {
+ if(cur_addr->FirstGatewayAddress &&
+ cur_addr->FirstGatewayAddress->Address.lpSockaddr) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *)
+ cur_addr->FirstGatewayAddress->Address.lpSockaddr;
+ next_hop->s_addr = ipv4->sin_addr.S_un.S_addr;
+ *netdev_name = xstrdup((char *)cur_addr->FriendlyName);
+
+ free(all_addr);
+
+ return 0;
+ }
+
+ cur_addr = cur_addr->Next;
+ }
+ } else {
+ VLOG_ERR("Call to GetAdaptersAddresses failed with error: %s",
+ ovs_format_message(ret_val));
+ }
+
+ if (all_addr) {
+ free(all_addr);
+ }
+ return ENXIO;
}
-\f
static int
netdev_windows_internal_construct(struct netdev *netdev_)
#define NETDEV_WINDOWS_CLASS(NAME, CONSTRUCT) \
{ \
.type = NAME, \
- .init = netdev_windows_init, \
.alloc = netdev_windows_alloc, \
.construct = CONSTRUCT, \
+ .destruct = netdev_windows_destruct, \
.dealloc = netdev_windows_dealloc, \
.get_etheraddr = netdev_windows_get_etheraddr, \
+ .set_etheraddr = netdev_windows_set_etheraddr, \
+ .update_flags = netdev_windows_update_flags, \
+ .get_next_hop = netdev_windows_get_next_hop, \
+ .arp_lookup = netdev_windows_arp_lookup, \
}
const struct netdev_class netdev_windows_class =