]>
Commit | Line | Data |
---|---|---|
064af421 | 1 | /* |
9b45d7f5 | 2 | * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks. |
064af421 | 3 | * |
a14bc59f BP |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
064af421 | 7 | * |
a14bc59f BP |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
064af421 BP |
15 | */ |
16 | ||
17 | #include <config.h> | |
18 | #include "learning-switch.h" | |
19 | ||
20 | #include <errno.h> | |
21 | #include <inttypes.h> | |
22 | #include <netinet/in.h> | |
23 | #include <stdlib.h> | |
24 | #include <time.h> | |
25 | ||
10a24935 | 26 | #include "byte-order.h" |
daa68e9f | 27 | #include "classifier.h" |
064af421 | 28 | #include "flow.h" |
d4cdc6b4 | 29 | #include "hmap.h" |
064af421 BP |
30 | #include "mac-learning.h" |
31 | #include "ofpbuf.h" | |
aaaa7553 | 32 | #include "ofp-parse.h" |
064af421 | 33 | #include "ofp-print.h" |
fa37b408 | 34 | #include "ofp-util.h" |
064af421 BP |
35 | #include "openflow/openflow.h" |
36 | #include "poll-loop.h" | |
064af421 | 37 | #include "rconn.h" |
d4cdc6b4 | 38 | #include "shash.h" |
064af421 BP |
39 | #include "timeval.h" |
40 | #include "vconn.h" | |
5136ce49 | 41 | #include "vlog.h" |
064af421 | 42 | |
d98e6007 | 43 | VLOG_DEFINE_THIS_MODULE(learning_switch); |
064af421 | 44 | |
d4cdc6b4 BP |
45 | struct lswitch_port { |
46 | struct hmap_node hmap_node; /* Hash node for port number. */ | |
47 | uint16_t port_no; /* OpenFlow port number, in host byte order. */ | |
48 | uint32_t queue_id; /* OpenFlow queue number. */ | |
49 | }; | |
50 | ||
064af421 BP |
51 | struct lswitch { |
52 | /* If nonnegative, the switch sets up flows that expire after the given | |
53 | * number of seconds (or never expire, if the value is OFP_FLOW_PERMANENT). | |
54 | * Otherwise, the switch processes every packet. */ | |
55 | int max_idle; | |
56 | ||
57 | unsigned long long int datapath_id; | |
064af421 BP |
58 | time_t last_features_request; |
59 | struct mac_learning *ml; /* NULL to act as hub instead of switch. */ | |
daa68e9f | 60 | struct flow_wildcards wc; /* Wildcards to apply to flows. */ |
9af9e2e8 | 61 | bool action_normal; /* Use OFPP_NORMAL? */ |
d4cdc6b4 BP |
62 | |
63 | /* Queue distribution. */ | |
64 | uint32_t default_queue; /* Default OpenFlow queue, or UINT32_MAX. */ | |
65 | struct hmap queue_numbers; /* Map from port number to lswitch_port. */ | |
66 | struct shash queue_names; /* Map from port name to lswitch_port. */ | |
064af421 BP |
67 | |
68 | /* Number of outgoing queued packets on the rconn. */ | |
69 | struct rconn_packet_counter *queued; | |
064af421 BP |
70 | }; |
71 | ||
72 | /* The log messages here could actually be useful in debugging, so keep the | |
73 | * rate limit relatively high. */ | |
74 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); | |
75 | ||
76 | static void queue_tx(struct lswitch *, struct rconn *, struct ofpbuf *); | |
77 | static void send_features_request(struct lswitch *, struct rconn *); | |
064af421 | 78 | |
d1e2cf21 BP |
79 | static void process_switch_features(struct lswitch *, |
80 | struct ofp_switch_features *); | |
81 | static void process_packet_in(struct lswitch *, struct rconn *, | |
82 | const struct ofp_packet_in *); | |
83 | static void process_echo_request(struct lswitch *, struct rconn *, | |
84 | const struct ofp_header *); | |
064af421 | 85 | |
ad67e568 BP |
86 | /* Creates and returns a new learning switch whose configuration is given by |
87 | * 'cfg'. | |
aaaa7553 | 88 | * |
064af421 BP |
89 | * 'rconn' is used to send out an OpenFlow features request. */ |
90 | struct lswitch * | |
ad67e568 | 91 | lswitch_create(struct rconn *rconn, const struct lswitch_config *cfg) |
064af421 BP |
92 | { |
93 | struct lswitch *sw; | |
064af421 | 94 | |
ec6fde61 | 95 | sw = xzalloc(sizeof *sw); |
ad67e568 | 96 | sw->max_idle = cfg->max_idle; |
064af421 BP |
97 | sw->datapath_id = 0; |
98 | sw->last_features_request = time_now() - 1; | |
ad67e568 BP |
99 | sw->ml = cfg->mode == LSW_LEARN ? mac_learning_create() : NULL; |
100 | sw->action_normal = cfg->mode == LSW_NORMAL; | |
daa68e9f BP |
101 | |
102 | flow_wildcards_init_exact(&sw->wc); | |
7286b1e1 BP |
103 | if (cfg->wildcards) { |
104 | uint32_t ofpfw; | |
105 | ||
106 | if (cfg->wildcards == UINT32_MAX) { | |
107 | /* Try to wildcard as many fields as possible, but we cannot | |
108 | * wildcard all fields. We need in_port to detect moves. We need | |
b05072ee BP |
109 | * Ethernet source and dest and VLAN VID to do L2 learning. */ |
110 | ofpfw = (OFPFW_DL_TYPE | OFPFW_DL_VLAN_PCP | |
111 | | OFPFW_NW_SRC_ALL | OFPFW_NW_DST_ALL | |
7286b1e1 BP |
112 | | OFPFW_NW_TOS | OFPFW_NW_PROTO |
113 | | OFPFW_TP_SRC | OFPFW_TP_DST); | |
114 | } else { | |
115 | ofpfw = cfg->wildcards; | |
116 | } | |
117 | ||
118 | ofputil_wildcard_from_openflow(ofpfw, &sw->wc); | |
52f4c6fa | 119 | } |
d4cdc6b4 BP |
120 | |
121 | sw->default_queue = cfg->default_queue; | |
122 | hmap_init(&sw->queue_numbers); | |
123 | shash_init(&sw->queue_names); | |
124 | if (cfg->port_queues) { | |
125 | struct shash_node *node; | |
126 | ||
127 | SHASH_FOR_EACH (node, cfg->port_queues) { | |
128 | struct lswitch_port *port = xmalloc(sizeof *port); | |
129 | hmap_node_nullify(&port->hmap_node); | |
130 | port->queue_id = (uintptr_t) node->data; | |
131 | shash_add(&sw->queue_names, node->name, port); | |
132 | } | |
133 | } | |
134 | ||
064af421 | 135 | sw->queued = rconn_packet_counter_create(); |
064af421 | 136 | send_features_request(sw, rconn); |
09913dfd | 137 | |
b3907fbc BP |
138 | if (cfg->default_flows) { |
139 | const struct ofpbuf *b; | |
09913dfd | 140 | |
b3907fbc | 141 | LIST_FOR_EACH (b, list_node, cfg->default_flows) { |
b85145b5 BP |
142 | struct ofpbuf *copy = ofpbuf_clone(b); |
143 | int error = rconn_send(rconn, copy, NULL); | |
144 | if (error) { | |
145 | VLOG_INFO_RL(&rl, "%s: failed to queue default flows (%s)", | |
146 | rconn_get_name(rconn), strerror(error)); | |
147 | ofpbuf_delete(copy); | |
148 | break; | |
149 | } | |
b3907fbc BP |
150 | } |
151 | } | |
152 | ||
064af421 BP |
153 | return sw; |
154 | } | |
155 | ||
156 | /* Destroys 'sw'. */ | |
157 | void | |
158 | lswitch_destroy(struct lswitch *sw) | |
159 | { | |
160 | if (sw) { | |
d4cdc6b4 BP |
161 | struct lswitch_port *node, *next; |
162 | ||
163 | HMAP_FOR_EACH_SAFE (node, next, hmap_node, &sw->queue_numbers) { | |
164 | hmap_remove(&sw->queue_numbers, &node->hmap_node); | |
165 | free(node); | |
166 | } | |
167 | shash_destroy(&sw->queue_names); | |
064af421 BP |
168 | mac_learning_destroy(sw->ml); |
169 | rconn_packet_counter_destroy(sw->queued); | |
170 | free(sw); | |
171 | } | |
172 | } | |
173 | ||
174 | /* Takes care of necessary 'sw' activity, except for receiving packets (which | |
175 | * the caller must do). */ | |
176 | void | |
ba186119 | 177 | lswitch_run(struct lswitch *sw) |
064af421 | 178 | { |
064af421 BP |
179 | if (sw->ml) { |
180 | mac_learning_run(sw->ml, NULL); | |
181 | } | |
064af421 BP |
182 | } |
183 | ||
184 | void | |
185 | lswitch_wait(struct lswitch *sw) | |
186 | { | |
187 | if (sw->ml) { | |
188 | mac_learning_wait(sw->ml); | |
189 | } | |
064af421 BP |
190 | } |
191 | ||
192 | /* Processes 'msg', which should be an OpenFlow received on 'rconn', according | |
193 | * to the learning switch state in 'sw'. The most likely result of processing | |
194 | * is that flow-setup and packet-out OpenFlow messages will be sent out on | |
195 | * 'rconn'. */ | |
196 | void | |
197 | lswitch_process_packet(struct lswitch *sw, struct rconn *rconn, | |
198 | const struct ofpbuf *msg) | |
199 | { | |
d1e2cf21 BP |
200 | const struct ofp_header *oh = msg->data; |
201 | const struct ofputil_msg_type *type; | |
202 | ||
064af421 BP |
203 | if (sw->datapath_id == 0 |
204 | && oh->type != OFPT_ECHO_REQUEST | |
205 | && oh->type != OFPT_FEATURES_REPLY) { | |
206 | send_features_request(sw, rconn); | |
207 | return; | |
208 | } | |
209 | ||
d1e2cf21 BP |
210 | ofputil_decode_msg_type(oh, &type); |
211 | switch (ofputil_msg_type_code(type)) { | |
212 | case OFPUTIL_OFPT_ECHO_REQUEST: | |
213 | process_echo_request(sw, rconn, msg->data); | |
214 | break; | |
215 | ||
216 | case OFPUTIL_OFPT_FEATURES_REPLY: | |
217 | process_switch_features(sw, msg->data); | |
218 | break; | |
219 | ||
220 | case OFPUTIL_OFPT_PACKET_IN: | |
221 | process_packet_in(sw, rconn, msg->data); | |
222 | break; | |
223 | ||
224 | case OFPUTIL_OFPT_FLOW_REMOVED: | |
225 | /* Nothing to do. */ | |
226 | break; | |
227 | ||
8f93e93c | 228 | case OFPUTIL_MSG_INVALID: |
d1e2cf21 BP |
229 | case OFPUTIL_OFPT_HELLO: |
230 | case OFPUTIL_OFPT_ERROR: | |
231 | case OFPUTIL_OFPT_ECHO_REPLY: | |
232 | case OFPUTIL_OFPT_FEATURES_REQUEST: | |
233 | case OFPUTIL_OFPT_GET_CONFIG_REQUEST: | |
234 | case OFPUTIL_OFPT_GET_CONFIG_REPLY: | |
235 | case OFPUTIL_OFPT_SET_CONFIG: | |
236 | case OFPUTIL_OFPT_PORT_STATUS: | |
237 | case OFPUTIL_OFPT_PACKET_OUT: | |
238 | case OFPUTIL_OFPT_FLOW_MOD: | |
239 | case OFPUTIL_OFPT_PORT_MOD: | |
240 | case OFPUTIL_OFPT_BARRIER_REQUEST: | |
241 | case OFPUTIL_OFPT_BARRIER_REPLY: | |
242 | case OFPUTIL_OFPT_QUEUE_GET_CONFIG_REQUEST: | |
243 | case OFPUTIL_OFPT_QUEUE_GET_CONFIG_REPLY: | |
244 | case OFPUTIL_OFPST_DESC_REQUEST: | |
245 | case OFPUTIL_OFPST_FLOW_REQUEST: | |
246 | case OFPUTIL_OFPST_AGGREGATE_REQUEST: | |
247 | case OFPUTIL_OFPST_TABLE_REQUEST: | |
248 | case OFPUTIL_OFPST_PORT_REQUEST: | |
249 | case OFPUTIL_OFPST_QUEUE_REQUEST: | |
250 | case OFPUTIL_OFPST_DESC_REPLY: | |
251 | case OFPUTIL_OFPST_FLOW_REPLY: | |
252 | case OFPUTIL_OFPST_QUEUE_REPLY: | |
253 | case OFPUTIL_OFPST_PORT_REPLY: | |
254 | case OFPUTIL_OFPST_TABLE_REPLY: | |
255 | case OFPUTIL_OFPST_AGGREGATE_REPLY: | |
d1e2cf21 BP |
256 | case OFPUTIL_NXT_ROLE_REQUEST: |
257 | case OFPUTIL_NXT_ROLE_REPLY: | |
6c1491fb | 258 | case OFPUTIL_NXT_FLOW_MOD_TABLE_ID: |
d1e2cf21 BP |
259 | case OFPUTIL_NXT_SET_FLOW_FORMAT: |
260 | case OFPUTIL_NXT_FLOW_MOD: | |
261 | case OFPUTIL_NXT_FLOW_REMOVED: | |
262 | case OFPUTIL_NXST_FLOW_REQUEST: | |
263 | case OFPUTIL_NXST_AGGREGATE_REQUEST: | |
264 | case OFPUTIL_NXST_FLOW_REPLY: | |
265 | case OFPUTIL_NXST_AGGREGATE_REPLY: | |
266 | default: | |
267 | if (VLOG_IS_DBG_ENABLED()) { | |
268 | char *s = ofp_to_string(msg->data, msg->size, 2); | |
269 | VLOG_DBG_RL(&rl, "%016llx: OpenFlow packet ignored: %s", | |
270 | sw->datapath_id, s); | |
271 | free(s); | |
064af421 BP |
272 | } |
273 | } | |
064af421 BP |
274 | } |
275 | \f | |
276 | static void | |
277 | send_features_request(struct lswitch *sw, struct rconn *rconn) | |
278 | { | |
279 | time_t now = time_now(); | |
280 | if (now >= sw->last_features_request + 1) { | |
281 | struct ofpbuf *b; | |
282 | struct ofp_switch_config *osc; | |
283 | ||
284 | /* Send OFPT_FEATURES_REQUEST. */ | |
285 | make_openflow(sizeof(struct ofp_header), OFPT_FEATURES_REQUEST, &b); | |
286 | queue_tx(sw, rconn, b); | |
287 | ||
288 | /* Send OFPT_SET_CONFIG. */ | |
289 | osc = make_openflow(sizeof *osc, OFPT_SET_CONFIG, &b); | |
064af421 BP |
290 | osc->miss_send_len = htons(OFP_DEFAULT_MISS_SEND_LEN); |
291 | queue_tx(sw, rconn, b); | |
292 | ||
293 | sw->last_features_request = now; | |
294 | } | |
295 | } | |
296 | ||
297 | static void | |
298 | queue_tx(struct lswitch *sw, struct rconn *rconn, struct ofpbuf *b) | |
299 | { | |
300 | int retval = rconn_send_with_limit(rconn, b, sw->queued, 10); | |
301 | if (retval && retval != ENOTCONN) { | |
302 | if (retval == EAGAIN) { | |
b123cc3c | 303 | VLOG_INFO_RL(&rl, "%016llx: %s: tx queue overflow", |
064af421 BP |
304 | sw->datapath_id, rconn_get_name(rconn)); |
305 | } else { | |
b123cc3c | 306 | VLOG_WARN_RL(&rl, "%016llx: %s: send: %s", |
064af421 BP |
307 | sw->datapath_id, rconn_get_name(rconn), |
308 | strerror(retval)); | |
309 | } | |
310 | } | |
311 | } | |
312 | ||
313 | static void | |
d1e2cf21 | 314 | process_switch_features(struct lswitch *sw, struct ofp_switch_features *osf) |
064af421 | 315 | { |
d4cdc6b4 BP |
316 | size_t n_ports; |
317 | size_t i; | |
318 | ||
064af421 | 319 | sw->datapath_id = ntohll(osf->datapath_id); |
d4cdc6b4 | 320 | |
d1e2cf21 | 321 | n_ports = (ntohs(osf->header.length) - sizeof *osf) / sizeof *osf->ports; |
d4cdc6b4 BP |
322 | for (i = 0; i < n_ports; i++) { |
323 | struct ofp_phy_port *opp = &osf->ports[i]; | |
324 | struct lswitch_port *lp; | |
325 | ||
326 | opp->name[OFP_MAX_PORT_NAME_LEN - 1] = '\0'; | |
0b61210e | 327 | lp = shash_find_data(&sw->queue_names, opp->name); |
d4cdc6b4 BP |
328 | if (lp && hmap_node_is_null(&lp->hmap_node)) { |
329 | lp->port_no = ntohs(opp->port_no); | |
330 | hmap_insert(&sw->queue_numbers, &lp->hmap_node, | |
331 | hash_int(lp->port_no, 0)); | |
332 | } | |
333 | } | |
064af421 BP |
334 | } |
335 | ||
81f3cad4 | 336 | static uint16_t |
ae412e7d | 337 | lswitch_choose_destination(struct lswitch *sw, const struct flow *flow) |
064af421 | 338 | { |
81f3cad4 | 339 | uint16_t out_port; |
064af421 | 340 | |
81f3cad4 | 341 | /* Learn the source MAC. */ |
db8077c3 BP |
342 | if (mac_learning_may_learn(sw->ml, flow->dl_src, 0)) { |
343 | struct mac_entry *mac = mac_learning_insert(sw->ml, flow->dl_src, 0); | |
1bfe9681 | 344 | if (mac_entry_is_new(mac) || mac->port.i != flow->in_port) { |
b123cc3c | 345 | VLOG_DBG_RL(&rl, "%016llx: learned that "ETH_ADDR_FMT" is on " |
064af421 | 346 | "port %"PRIu16, sw->datapath_id, |
81f3cad4 | 347 | ETH_ADDR_ARGS(flow->dl_src), flow->in_port); |
db8077c3 | 348 | |
1bfe9681 | 349 | mac->port.i = flow->in_port; |
db8077c3 | 350 | mac_learning_changed(sw->ml, mac); |
064af421 BP |
351 | } |
352 | } | |
353 | ||
5a003f60 | 354 | /* Drop frames for reserved multicast addresses. */ |
81f3cad4 BP |
355 | if (eth_addr_is_reserved(flow->dl_dst)) { |
356 | return OFPP_NONE; | |
064af421 BP |
357 | } |
358 | ||
81f3cad4 | 359 | out_port = OFPP_FLOOD; |
064af421 | 360 | if (sw->ml) { |
db8077c3 BP |
361 | struct mac_entry *mac; |
362 | ||
363 | mac = mac_learning_lookup(sw->ml, flow->dl_dst, 0, NULL); | |
364 | if (mac) { | |
1bfe9681 | 365 | out_port = mac->port.i; |
81f3cad4 BP |
366 | if (out_port == flow->in_port) { |
367 | /* Don't send a packet back out its input port. */ | |
368 | return OFPP_NONE; | |
369 | } | |
064af421 BP |
370 | } |
371 | } | |
372 | ||
81f3cad4 BP |
373 | /* Check if we need to use "NORMAL" action. */ |
374 | if (sw->action_normal && out_port != OFPP_FLOOD) { | |
375 | return OFPP_NORMAL; | |
376 | } | |
377 | ||
378 | return out_port; | |
379 | } | |
380 | ||
d4cdc6b4 BP |
381 | static uint32_t |
382 | get_queue_id(const struct lswitch *sw, uint16_t in_port) | |
383 | { | |
384 | const struct lswitch_port *port; | |
385 | ||
386 | HMAP_FOR_EACH_WITH_HASH (port, hmap_node, hash_int(in_port, 0), | |
387 | &sw->queue_numbers) { | |
388 | if (port->port_no == in_port) { | |
389 | return port->queue_id; | |
390 | } | |
391 | } | |
392 | ||
393 | return sw->default_queue; | |
394 | } | |
395 | ||
81f3cad4 | 396 | static void |
d1e2cf21 BP |
397 | process_packet_in(struct lswitch *sw, struct rconn *rconn, |
398 | const struct ofp_packet_in *opi) | |
81f3cad4 | 399 | { |
81f3cad4 | 400 | uint16_t in_port = ntohs(opi->in_port); |
d4cdc6b4 | 401 | uint32_t queue_id; |
81f3cad4 BP |
402 | uint16_t out_port; |
403 | ||
c71270b7 BP |
404 | struct ofp_action_header actions[2]; |
405 | size_t actions_len; | |
406 | ||
81f3cad4 BP |
407 | size_t pkt_ofs, pkt_len; |
408 | struct ofpbuf pkt; | |
ae412e7d | 409 | struct flow flow; |
81f3cad4 | 410 | |
6699af68 BP |
411 | /* Ignore packets sent via output to OFPP_CONTROLLER. This library never |
412 | * uses such an action. You never know what experiments might be going on, | |
413 | * though, and it seems best not to interfere with them. */ | |
414 | if (opi->reason != OFPR_NO_MATCH) { | |
415 | return; | |
416 | } | |
417 | ||
81f3cad4 BP |
418 | /* Extract flow data from 'opi' into 'flow'. */ |
419 | pkt_ofs = offsetof(struct ofp_packet_in, data); | |
420 | pkt_len = ntohs(opi->header.length) - pkt_ofs; | |
0bc9407d | 421 | ofpbuf_use_const(&pkt, opi->data, pkt_len); |
81f3cad4 BP |
422 | flow_extract(&pkt, 0, in_port, &flow); |
423 | ||
424 | /* Choose output port. */ | |
425 | out_port = lswitch_choose_destination(sw, &flow); | |
426 | ||
c71270b7 | 427 | /* Make actions. */ |
d4cdc6b4 | 428 | queue_id = get_queue_id(sw, in_port); |
c71270b7 BP |
429 | if (out_port == OFPP_NONE) { |
430 | actions_len = 0; | |
d4cdc6b4 | 431 | } else if (queue_id == UINT32_MAX || out_port >= OFPP_MAX) { |
3a929702 BP |
432 | struct ofp_action_output oao; |
433 | ||
434 | memset(&oao, 0, sizeof oao); | |
435 | oao.type = htons(OFPAT_OUTPUT); | |
436 | oao.len = htons(sizeof oao); | |
437 | oao.port = htons(out_port); | |
438 | ||
439 | memcpy(actions, &oao, sizeof oao); | |
440 | actions_len = sizeof oao; | |
c71270b7 | 441 | } else { |
3a929702 BP |
442 | struct ofp_action_enqueue oae; |
443 | ||
444 | memset(&oae, 0, sizeof oae); | |
445 | oae.type = htons(OFPAT_ENQUEUE); | |
446 | oae.len = htons(sizeof oae); | |
447 | oae.port = htons(out_port); | |
d4cdc6b4 | 448 | oae.queue_id = htonl(queue_id); |
3a929702 BP |
449 | |
450 | memcpy(actions, &oae, sizeof oae); | |
451 | actions_len = sizeof oae; | |
c71270b7 BP |
452 | } |
453 | assert(actions_len <= sizeof actions); | |
454 | ||
81f3cad4 BP |
455 | /* Send the packet, and possibly the whole flow, to the output port. */ |
456 | if (sw->max_idle >= 0 && (!sw->ml || out_port != OFPP_FLOOD)) { | |
9af9e2e8 | 457 | struct ofpbuf *buffer; |
daa68e9f | 458 | struct cls_rule rule; |
9af9e2e8 | 459 | |
064af421 BP |
460 | /* The output port is known, or we always flood everything, so add a |
461 | * new flow. */ | |
daa68e9f BP |
462 | cls_rule_init(&flow, &sw->wc, 0, &rule); |
463 | buffer = make_add_flow(&rule, ntohl(opi->buffer_id), | |
c71270b7 BP |
464 | sw->max_idle, actions_len); |
465 | ofpbuf_put(buffer, actions, actions_len); | |
9af9e2e8 | 466 | queue_tx(sw, rconn, buffer); |
064af421 BP |
467 | |
468 | /* If the switch didn't buffer the packet, we need to send a copy. */ | |
c71270b7 | 469 | if (ntohl(opi->buffer_id) == UINT32_MAX && actions_len > 0) { |
064af421 | 470 | queue_tx(sw, rconn, |
c71270b7 BP |
471 | make_packet_out(&pkt, UINT32_MAX, in_port, |
472 | actions, actions_len / sizeof *actions)); | |
064af421 BP |
473 | } |
474 | } else { | |
475 | /* We don't know that MAC, or we don't set up flows. Send along the | |
476 | * packet without setting up a flow. */ | |
c71270b7 | 477 | if (ntohl(opi->buffer_id) != UINT32_MAX || actions_len > 0) { |
81f3cad4 | 478 | queue_tx(sw, rconn, |
c71270b7 BP |
479 | make_packet_out(&pkt, ntohl(opi->buffer_id), in_port, |
480 | actions, actions_len / sizeof *actions)); | |
064af421 | 481 | } |
064af421 | 482 | } |
064af421 BP |
483 | } |
484 | ||
485 | static void | |
d1e2cf21 BP |
486 | process_echo_request(struct lswitch *sw, struct rconn *rconn, |
487 | const struct ofp_header *rq) | |
064af421 | 488 | { |
064af421 BP |
489 | queue_tx(sw, rconn, make_echo_reply(rq)); |
490 | } |