OpenFlow1.0.0 代码解读——add port

此文章解析主函数中没有详细解读的 add_ports 函数。

主函数引用调用如下:

1
2
3
if (port_list) {
add_ports(dp, port_list);
}

在函数开始时,将 port_list 里的端口打开。

add_port

strtok_r 函数相当于是 linux 平台下的 strtok 函数,主要用于分隔结构体的各成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void
add_ports(struct datapath *dp, char *port_list)
{
char *port, *save_ptr;
/* Glibc 2.7 has a bug in strtok_r when compiling with optimization that
* can cause segfaults here:
* http://sources.redhat.com/bugzilla/show_bug.cgi?id=5614.
* Using ",," instead of the obvious "," works around it. */
for (port = strtok_r(port_list, ",,", &save_ptr); port;
port = strtok_r(NULL, ",,", &save_ptr)) {
int error = dp_add_port(dp, port, num_queues);
if (error) {
ofp_fatal(error, "failed to add port %s", port);
}
}
}

函数首先对 port_list 结构体做处理,赋值到参数 port 中,port 作为具体的端口名,调用 dp_add_port 函数。

dp_add_port

此函数中的 port 与上一层函数的 port 不同,add_port 中的 port 是一个 char 型指针变量,指向端口名。这一函数中的 port 是 sw_port 结构体的指针,具体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct sw_port {
uint32_t config; /* Some subset of OFPPC_* flags. */
uint32_t state; /* Some subset of OFPPS_* flags. */
struct datapath *dp;
struct netdev *netdev;
struct list node; /* Element in datapath.ports. */
unsigned long long int rx_packets, tx_packets;
unsigned long long int rx_bytes, tx_bytes;
unsigned long long int tx_dropped;
uint16_t port_no;
/* port queues */
uint16_t num_queues;
struct sw_queue queues[NETDEV_MAX_QUEUES];
struct list queue_list; /* list of all queues for this port */
};

sw_port 结构体中包含具体的网络端口 netdev ,所以函数判断 port 结构体中的 netdev 是否为空,从而判断端口是否打开。没有打开则调用 new_port 函数打开端口。

1
2
3
4
5
6
7
8
9
10
11
12
int
dp_add_port(struct datapath *dp, const char *netdev, uint16_t num_queues)
{
int port_no;
for (port_no = 1; port_no < DP_MAX_PORTS; port_no++) {
struct sw_port *port = &dp->ports[port_no];
if (!port->netdev) {
return new_port(dp, port, port_no, netdev, NULL, num_queues);
}
}
return EXFULL;
}

new_port

该函数的主要功能包括:

  • 调用 netdev_open 函数打开网络设备,成功则返回0。具体的网络设备处理由 do_open_netdev 执行。
  • 设置网络设备的mac地址
  • 设置flags
  • 设置ip地址
  • 添加信息到 port 结构体中
  • 调用 send_port_status 将端口设置成功的消息发送到与controller之间的安全信道

函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
static int
new_port(struct datapath *dp, struct sw_port *port, uint16_t port_no,
const char *netdev_name, const uint8_t *new_mac, uint16_t num_queues)
{
struct netdev *netdev;
struct in6_addr in6;
struct in_addr in4;
int error;

error = netdev_open(netdev_name, NETDEV_ETH_TYPE_ANY, &netdev);
if (error) {
return error;
}
if (new_mac && !eth_addr_equals(netdev_get_etheraddr(netdev), new_mac)) {
/* Generally the device has to be down before we change its hardware
* address. Don't bother to check for an error because it's really
* the netdev_set_etheraddr() call below that we care about. */
netdev_set_flags(netdev, 0, false);
error = netdev_set_etheraddr(netdev, new_mac);
if (error) {
VLOG_WARN("failed to change %s Ethernet address "
"to "ETH_ADDR_FMT": %s",
netdev_name, ETH_ADDR_ARGS(new_mac), strerror(error));
}
}
error = netdev_set_flags(netdev, NETDEV_UP | NETDEV_PROMISC, false);
if (error) {
VLOG_ERR("failed to set promiscuous mode on %s device", netdev_name);
netdev_close(netdev);
return error;
}
if (netdev_get_in4(netdev, &in4)) {
VLOG_ERR("%s device has assigned IP address %s",
netdev_name, inet_ntoa(in4));
}
if (netdev_get_in6(netdev, &in6)) {
char in6_name[INET6_ADDRSTRLEN + 1];
inet_ntop(AF_INET6, &in6, in6_name, sizeof in6_name);
VLOG_ERR("%s device has assigned IPv6 address %s",
netdev_name, in6_name);
}

if (num_queues > 0) {
error = netdev_setup_slicing(netdev, num_queues);
if (error) {
VLOG_ERR("failed to configure slicing on %s device: "\
"check INSTALL for dependencies, or rerun "\
"using --no-slicing option to disable slicing",
netdev_name);
netdev_close(netdev);
return error;
}
}

memset(port, '\0', sizeof *port);

list_init(&port->queue_list);
port->dp = dp;
port->netdev = netdev;
port->port_no = port_no;
port->num_queues = num_queues;
list_push_back(&dp->port_list, &port->node);

/* Notify the ctlpath that this port has been added */
send_port_status(port, OFPPR_ADD);

return 0;
}

可以重点看一下 do_open_netdevsend_port_status 函数。

do_open_netdev

此函数是以 netdev_open 函数作为入口被调用的,可以简单看一下 netdev_open 函数。

netdev_open

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Opens the network device named 'name' (e.g. "eth0") and returns zero if
* successful, otherwise a positive errno value. On success, sets '*netdevp'
* 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 **netdevp)
{
if (!strncmp(name, "tap:", 4)) {
return netdev_open_tap(name + 4, netdevp);
} else {
return do_open_netdev(name, ethertype, -1, netdevp);
}
}

总体比较简单,需要特地说明的是输入参数 ethertype 。该参数在 netdev_open 中的输入是 NETDEV_ETH_TYPE_ANY 。在 netdev.h 中有具体定义:

1
2
3
4
5
enum netdev_pseudo_ethertype {
NETDEV_ETH_TYPE_NONE = -128, /* Receive no frames. */
NETDEV_ETH_TYPE_ANY, /* Receive all frames. */
NETDEV_ETH_TYPE_802_2 /* Receive all IEEE 802.2 frames. */
};

回到 do_open_netdev 函数,该函数代码如下,不过多解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
static int
do_open_netdev(const char *name, int ethertype, int tap_fd,
struct netdev **netdev_)
{
int netdev_fd;
struct sockaddr_ll sll;
struct ifreq ifr;
unsigned int ifindex;
uint8_t etheraddr[ETH_ADDR_LEN];
struct in6_addr in6;
int mtu;
int txqlen;
int hwaddr_family;
int error;
struct netdev *netdev;

init_netdev();
*netdev_ = NULL;

/* Create raw socket. */
netdev_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 (netdev_fd < 0) {
return errno;
}

/* Set non-blocking mode. */
error = set_nonblocking(netdev_fd);
if (error) {
goto error_already_set;
}

/* Get ethernet device index. */
strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
if (ioctl(netdev_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(netdev_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(netdev_fd);
if (error) {
goto error;
}
}

/* Get MAC address. */
if (ioctl(netdev_fd, SIOCGIFHWADDR, &ifr) < 0) {
VLOG_ERR("ioctl(SIOCGIFHWADDR) on %s device failed: %s",
name, strerror(errno));
goto error;
}
hwaddr_family = ifr.ifr_hwaddr.sa_family;
if (hwaddr_family != AF_UNSPEC && hwaddr_family != ARPHRD_ETHER) {
VLOG_WARN("%s device has unknown hardware address family %d",
name, hwaddr_family);
}
memcpy(etheraddr, ifr.ifr_hwaddr.sa_data, sizeof etheraddr);

/* Get MTU. */
if (ioctl(netdev_fd, SIOCGIFMTU, &ifr) < 0) {
VLOG_ERR("ioctl(SIOCGIFMTU) on %s device failed: %s",
name, strerror(errno));
goto error;
}
mtu = ifr.ifr_mtu;

/* Get TX queue length. */
if (ioctl(netdev_fd, SIOCGIFTXQLEN, &ifr) < 0) {
VLOG_ERR("ioctl(SIOCGIFTXQLEN) on %s device failed: %s",
name, strerror(errno));
goto error;
}
txqlen = ifr.ifr_qlen;

get_ipv6_address(name, &in6);

/* Allocate network device. */
netdev = xmalloc(sizeof *netdev);
netdev->name = xstrdup(name);
netdev->ifindex = ifindex;
netdev->txqlen = txqlen;
netdev->hwaddr_family = hwaddr_family;
netdev->netdev_fd = netdev_fd;
netdev->tap_fd = tap_fd < 0 ? netdev_fd : tap_fd;
netdev->queue_fd[0] = netdev->tap_fd;
memcpy(netdev->etheraddr, etheraddr, sizeof etheraddr);
netdev->mtu = mtu;
netdev->in6 = in6;
netdev->num_queues = 0;

/* Get speed, features. */
do_ethtool(netdev);

/* Save flags to restore at close or exit. */
error = get_flags(netdev->name, &netdev->save_flags);
if (error) {
goto error_already_set;
}
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;
error_already_set:
close(netdev_fd);
if (tap_fd >= 0) {
close(tap_fd);
}
return error;
}