]>
Commit | Line | Data |
---|---|---|
f22716dc | 1 | /* |
cc40d06b | 2 | * Copyright (c) 2010, 2011, 2012, 2013, 2014 Nicira, Inc. |
f22716dc JP |
3 | * |
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: | |
7 | * | |
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. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | ||
19 | #include "ofp-parse.h" | |
20 | ||
00b1c62f | 21 | #include <ctype.h> |
f22716dc JP |
22 | #include <errno.h> |
23 | #include <stdlib.h> | |
24 | ||
daff3353 | 25 | #include "bundle.h" |
10a24935 | 26 | #include "byte-order.h" |
15f1f1b6 | 27 | #include "dynamic-string.h" |
75a75043 | 28 | #include "learn.h" |
6a885fd0 | 29 | #include "meta-flow.h" |
53ddd40a | 30 | #include "multipath.h" |
f25d0cf3 | 31 | #include "netdev.h" |
f393f81e | 32 | #include "nx-match.h" |
f25d0cf3 | 33 | #include "ofp-actions.h" |
f22716dc JP |
34 | #include "ofp-util.h" |
35 | #include "ofpbuf.h" | |
36 | #include "openflow/openflow.h" | |
a944ef40 | 37 | #include "ovs-thread.h" |
f22716dc | 38 | #include "packets.h" |
5a0a5702 | 39 | #include "simap.h" |
f22716dc JP |
40 | #include "socket-util.h" |
41 | #include "vconn.h" | |
f22716dc | 42 | |
bdda5aca BP |
43 | /* Parses 'str' as an 8-bit unsigned integer into '*valuep'. |
44 | * | |
45 | * 'name' describes the value parsed in an error message, if any. | |
46 | * | |
47 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
48 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 49 | char * OVS_WARN_UNUSED_RESULT |
bdda5aca | 50 | str_to_u8(const char *str, const char *name, uint8_t *valuep) |
c3636ffc | 51 | { |
638a19b0 | 52 | int value; |
c3636ffc | 53 | |
bdda5aca BP |
54 | if (!str_to_int(str, 0, &value) || value < 0 || value > 255) { |
55 | return xasprintf("invalid %s \"%s\"", name, str); | |
c3636ffc | 56 | } |
bdda5aca BP |
57 | *valuep = value; |
58 | return NULL; | |
c3636ffc BP |
59 | } |
60 | ||
bdda5aca BP |
61 | /* Parses 'str' as a 16-bit unsigned integer into '*valuep'. |
62 | * | |
63 | * 'name' describes the value parsed in an error message, if any. | |
64 | * | |
65 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
66 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 67 | char * OVS_WARN_UNUSED_RESULT |
bdda5aca | 68 | str_to_u16(const char *str, const char *name, uint16_t *valuep) |
c3636ffc BP |
69 | { |
70 | int value; | |
71 | ||
72 | if (!str_to_int(str, 0, &value) || value < 0 || value > 65535) { | |
bdda5aca | 73 | return xasprintf("invalid %s \"%s\"", name, str); |
c3636ffc | 74 | } |
bdda5aca BP |
75 | *valuep = value; |
76 | return NULL; | |
c3636ffc BP |
77 | } |
78 | ||
bdda5aca BP |
79 | /* Parses 'str' as a 32-bit unsigned integer into '*valuep'. |
80 | * | |
81 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
82 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 83 | char * OVS_WARN_UNUSED_RESULT |
bdda5aca | 84 | str_to_u32(const char *str, uint32_t *valuep) |
f22716dc JP |
85 | { |
86 | char *tail; | |
87 | uint32_t value; | |
88 | ||
c4894ed4 | 89 | if (!str[0]) { |
bdda5aca | 90 | return xstrdup("missing required numeric argument"); |
ce5452cf EJ |
91 | } |
92 | ||
f22716dc JP |
93 | errno = 0; |
94 | value = strtoul(str, &tail, 0); | |
95 | if (errno == EINVAL || errno == ERANGE || *tail) { | |
bdda5aca | 96 | return xasprintf("invalid numeric format %s", str); |
f22716dc | 97 | } |
bdda5aca BP |
98 | *valuep = value; |
99 | return NULL; | |
f22716dc JP |
100 | } |
101 | ||
bdda5aca BP |
102 | /* Parses 'str' as an 64-bit unsigned integer into '*valuep'. |
103 | * | |
104 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
105 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 106 | char * OVS_WARN_UNUSED_RESULT |
bdda5aca | 107 | str_to_u64(const char *str, uint64_t *valuep) |
f22716dc JP |
108 | { |
109 | char *tail; | |
110 | uint64_t value; | |
111 | ||
c4894ed4 | 112 | if (!str[0]) { |
bdda5aca | 113 | return xstrdup("missing required numeric argument"); |
c4894ed4 BP |
114 | } |
115 | ||
f22716dc JP |
116 | errno = 0; |
117 | value = strtoull(str, &tail, 0); | |
118 | if (errno == EINVAL || errno == ERANGE || *tail) { | |
bdda5aca | 119 | return xasprintf("invalid numeric format %s", str); |
f22716dc | 120 | } |
bdda5aca BP |
121 | *valuep = value; |
122 | return NULL; | |
f22716dc JP |
123 | } |
124 | ||
bdda5aca BP |
125 | /* Parses 'str' as an 64-bit unsigned integer in network byte order into |
126 | * '*valuep'. | |
127 | * | |
128 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
129 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 130 | char * OVS_WARN_UNUSED_RESULT |
bdda5aca BP |
131 | str_to_be64(const char *str, ovs_be64 *valuep) |
132 | { | |
4be17953 | 133 | uint64_t value = 0; |
bdda5aca BP |
134 | char *error; |
135 | ||
136 | error = str_to_u64(str, &value); | |
137 | if (!error) { | |
138 | *valuep = htonll(value); | |
139 | } | |
140 | return error; | |
141 | } | |
142 | ||
143 | /* Parses 'str' as an Ethernet address into 'mac'. | |
144 | * | |
145 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
146 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 147 | char * OVS_WARN_UNUSED_RESULT |
3bd0fd39 | 148 | str_to_mac(const char *str, uint8_t mac[ETH_ADDR_LEN]) |
f22716dc | 149 | { |
c2c28dfd | 150 | if (!ovs_scan(str, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { |
bdda5aca | 151 | return xasprintf("invalid mac address %s", str); |
f22716dc | 152 | } |
bdda5aca | 153 | return NULL; |
f22716dc JP |
154 | } |
155 | ||
bdda5aca BP |
156 | /* Parses 'str' as an IP address into '*ip'. |
157 | * | |
158 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
159 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 160 | char * OVS_WARN_UNUSED_RESULT |
6a885fd0 | 161 | str_to_ip(const char *str, ovs_be32 *ip) |
cb8ca532 | 162 | { |
f22716dc | 163 | struct in_addr in_addr; |
f22716dc | 164 | |
6a885fd0 | 165 | if (lookup_ip(str, &in_addr)) { |
bdda5aca | 166 | return xasprintf("%s: could not convert to IP address", str); |
f22716dc JP |
167 | } |
168 | *ip = in_addr.s_addr; | |
bdda5aca | 169 | return NULL; |
d31f1109 JP |
170 | } |
171 | ||
f22716dc JP |
172 | struct protocol { |
173 | const char *name; | |
174 | uint16_t dl_type; | |
175 | uint8_t nw_proto; | |
176 | }; | |
177 | ||
178 | static bool | |
179 | parse_protocol(const char *name, const struct protocol **p_out) | |
180 | { | |
181 | static const struct protocol protocols[] = { | |
182 | { "ip", ETH_TYPE_IP, 0 }, | |
183 | { "arp", ETH_TYPE_ARP, 0 }, | |
6767a2cc JP |
184 | { "icmp", ETH_TYPE_IP, IPPROTO_ICMP }, |
185 | { "tcp", ETH_TYPE_IP, IPPROTO_TCP }, | |
186 | { "udp", ETH_TYPE_IP, IPPROTO_UDP }, | |
0d56eaf2 | 187 | { "sctp", ETH_TYPE_IP, IPPROTO_SCTP }, |
d31f1109 JP |
188 | { "ipv6", ETH_TYPE_IPV6, 0 }, |
189 | { "ip6", ETH_TYPE_IPV6, 0 }, | |
190 | { "icmp6", ETH_TYPE_IPV6, IPPROTO_ICMPV6 }, | |
191 | { "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP }, | |
192 | { "udp6", ETH_TYPE_IPV6, IPPROTO_UDP }, | |
0d56eaf2 | 193 | { "sctp6", ETH_TYPE_IPV6, IPPROTO_SCTP }, |
8087f5ff | 194 | { "rarp", ETH_TYPE_RARP, 0}, |
b02475c5 SH |
195 | { "mpls", ETH_TYPE_MPLS, 0 }, |
196 | { "mplsm", ETH_TYPE_MPLS_MCAST, 0 }, | |
197 | }; | |
f22716dc JP |
198 | const struct protocol *p; |
199 | ||
200 | for (p = protocols; p < &protocols[ARRAY_SIZE(protocols)]; p++) { | |
201 | if (!strcmp(p->name, name)) { | |
202 | *p_out = p; | |
203 | return true; | |
204 | } | |
205 | } | |
206 | *p_out = NULL; | |
207 | return false; | |
208 | } | |
209 | ||
bdda5aca | 210 | /* Parses 's' as the (possibly masked) value of field 'mf', and updates |
db0b6c29 JR |
211 | * 'match' appropriately. Restricts the set of usable protocols to ones |
212 | * supporting the parsed field. | |
bdda5aca BP |
213 | * |
214 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
215 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 216 | static char * OVS_WARN_UNUSED_RESULT |
db0b6c29 JR |
217 | parse_field(const struct mf_field *mf, const char *s, struct match *match, |
218 | enum ofputil_protocol *usable_protocols) | |
8050b31d | 219 | { |
6a885fd0 BP |
220 | union mf_value value, mask; |
221 | char *error; | |
bad68a99 | 222 | |
6a885fd0 | 223 | error = mf_parse(mf, s, &value, &mask); |
bdda5aca | 224 | if (!error) { |
db0b6c29 | 225 | *usable_protocols &= mf_set(mf, &value, &mask, match); |
8050b31d | 226 | } |
bdda5aca | 227 | return error; |
00b1c62f BP |
228 | } |
229 | ||
c2d936a4 BP |
230 | static char * |
231 | extract_actions(char *s) | |
232 | { | |
233 | s = strstr(s, "action"); | |
234 | if (s) { | |
235 | *s = '\0'; | |
236 | s = strchr(s + 1, '='); | |
237 | return s ? s + 1 : NULL; | |
238 | } else { | |
239 | return NULL; | |
240 | } | |
241 | } | |
242 | ||
243 | ||
cab50449 | 244 | static char * OVS_WARN_UNUSED_RESULT |
db0b6c29 | 245 | parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string, |
ba2fe8e9 | 246 | enum ofputil_protocol *usable_protocols) |
f22716dc | 247 | { |
c821124b BP |
248 | enum { |
249 | F_OUT_PORT = 1 << 0, | |
250 | F_ACTIONS = 1 << 1, | |
ca26eb44 | 251 | F_IMPORTANCE = 1 << 2, |
c821124b | 252 | F_TIMEOUT = 1 << 3, |
a993007b BP |
253 | F_PRIORITY = 1 << 4, |
254 | F_FLAGS = 1 << 5, | |
c821124b | 255 | } fields; |
f22716dc | 256 | char *save_ptr = NULL; |
75a75043 | 257 | char *act_str = NULL; |
f22716dc | 258 | char *name; |
f22716dc | 259 | |
db0b6c29 JR |
260 | *usable_protocols = OFPUTIL_P_ANY; |
261 | ||
c821124b BP |
262 | switch (command) { |
263 | case -1: | |
264 | fields = F_OUT_PORT; | |
265 | break; | |
266 | ||
267 | case OFPFC_ADD: | |
ca26eb44 | 268 | fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS | F_IMPORTANCE; |
c821124b BP |
269 | break; |
270 | ||
271 | case OFPFC_DELETE: | |
272 | fields = F_OUT_PORT; | |
273 | break; | |
274 | ||
275 | case OFPFC_DELETE_STRICT: | |
276 | fields = F_OUT_PORT | F_PRIORITY; | |
277 | break; | |
278 | ||
279 | case OFPFC_MODIFY: | |
a993007b | 280 | fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS; |
c821124b BP |
281 | break; |
282 | ||
283 | case OFPFC_MODIFY_STRICT: | |
a993007b | 284 | fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS; |
c821124b BP |
285 | break; |
286 | ||
287 | default: | |
428b2edd | 288 | OVS_NOT_REACHED(); |
c821124b BP |
289 | } |
290 | ||
81a76618 BP |
291 | match_init_catchall(&fm->match); |
292 | fm->priority = OFP_DEFAULT_PRIORITY; | |
88ca35ee | 293 | fm->cookie = htonll(0); |
e729e793 | 294 | fm->cookie_mask = htonll(0); |
623e1caf JP |
295 | if (command == OFPFC_MODIFY || command == OFPFC_MODIFY_STRICT) { |
296 | /* For modify, by default, don't update the cookie. */ | |
b8266395 | 297 | fm->new_cookie = OVS_BE64_MAX; |
623e1caf JP |
298 | } else{ |
299 | fm->new_cookie = htonll(0); | |
300 | } | |
23342857 | 301 | fm->modify_cookie = false; |
6c1491fb | 302 | fm->table_id = 0xff; |
c821124b | 303 | fm->command = command; |
88ca35ee BP |
304 | fm->idle_timeout = OFP_FLOW_PERMANENT; |
305 | fm->hard_timeout = OFP_FLOW_PERMANENT; | |
306 | fm->buffer_id = UINT32_MAX; | |
7f05e7ab | 307 | fm->out_port = OFPP_ANY; |
88ca35ee | 308 | fm->flags = 0; |
ca26eb44 | 309 | fm->importance = 0; |
7395c052 | 310 | fm->out_group = OFPG11_ANY; |
cc40d06b | 311 | fm->delete_reason = OFPRR_DELETE; |
c821124b | 312 | if (fields & F_ACTIONS) { |
c2d936a4 | 313 | act_str = extract_actions(string); |
f22716dc | 314 | if (!act_str) { |
bdda5aca | 315 | return xstrdup("must specify an action"); |
f22716dc | 316 | } |
f22716dc | 317 | } |
f22716dc JP |
318 | for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; |
319 | name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { | |
320 | const struct protocol *p; | |
bdda5aca | 321 | char *error = NULL; |
f22716dc JP |
322 | |
323 | if (parse_protocol(name, &p)) { | |
81a76618 | 324 | match_set_dl_type(&fm->match, htons(p->dl_type)); |
f22716dc | 325 | if (p->nw_proto) { |
81a76618 | 326 | match_set_nw_proto(&fm->match, p->nw_proto); |
f22716dc | 327 | } |
a993007b | 328 | } else if (fields & F_FLAGS && !strcmp(name, "send_flow_rem")) { |
0fb88c18 | 329 | fm->flags |= OFPUTIL_FF_SEND_FLOW_REM; |
a993007b | 330 | } else if (fields & F_FLAGS && !strcmp(name, "check_overlap")) { |
0fb88c18 | 331 | fm->flags |= OFPUTIL_FF_CHECK_OVERLAP; |
2e1ae200 | 332 | } else if (fields & F_FLAGS && !strcmp(name, "reset_counts")) { |
0fb88c18 | 333 | fm->flags |= OFPUTIL_FF_RESET_COUNTS; |
db0b6c29 | 334 | *usable_protocols &= OFPUTIL_P_OF12_UP; |
2e1ae200 | 335 | } else if (fields & F_FLAGS && !strcmp(name, "no_packet_counts")) { |
0fb88c18 | 336 | fm->flags |= OFPUTIL_FF_NO_PKT_COUNTS; |
db0b6c29 | 337 | *usable_protocols &= OFPUTIL_P_OF13_UP; |
2e1ae200 | 338 | } else if (fields & F_FLAGS && !strcmp(name, "no_byte_counts")) { |
0fb88c18 | 339 | fm->flags |= OFPUTIL_FF_NO_BYT_COUNTS; |
db0b6c29 | 340 | *usable_protocols &= OFPUTIL_P_OF13_UP; |
adcf00ba AZ |
341 | } else if (!strcmp(name, "no_readonly_table") |
342 | || !strcmp(name, "allow_hidden_fields")) { | |
343 | /* ignore these fields. */ | |
f22716dc | 344 | } else { |
f22716dc JP |
345 | char *value; |
346 | ||
347 | value = strtok_r(NULL, ", \t\r\n", &save_ptr); | |
348 | if (!value) { | |
bdda5aca | 349 | return xasprintf("field %s missing value", name); |
f22716dc JP |
350 | } |
351 | ||
6c1491fb | 352 | if (!strcmp(name, "table")) { |
bdda5aca | 353 | error = str_to_u8(value, "table", &fm->table_id); |
db0b6c29 JR |
354 | if (fm->table_id != 0xff) { |
355 | *usable_protocols &= OFPUTIL_P_TID; | |
356 | } | |
8050b31d | 357 | } else if (!strcmp(name, "out_port")) { |
be3f512a | 358 | if (!ofputil_port_from_string(value, &fm->out_port)) { |
bdda5aca BP |
359 | error = xasprintf("%s is not a valid OpenFlow port", |
360 | value); | |
c6100d92 | 361 | } |
c821124b | 362 | } else if (fields & F_PRIORITY && !strcmp(name, "priority")) { |
4be17953 | 363 | uint16_t priority = 0; |
bdda5aca BP |
364 | |
365 | error = str_to_u16(value, name, &priority); | |
366 | fm->priority = priority; | |
c821124b | 367 | } else if (fields & F_TIMEOUT && !strcmp(name, "idle_timeout")) { |
bdda5aca | 368 | error = str_to_u16(value, name, &fm->idle_timeout); |
c821124b | 369 | } else if (fields & F_TIMEOUT && !strcmp(name, "hard_timeout")) { |
bdda5aca | 370 | error = str_to_u16(value, name, &fm->hard_timeout); |
ca26eb44 RB |
371 | } else if (fields & F_IMPORTANCE && !strcmp(name, "importance")) { |
372 | error = str_to_u16(value, name, &fm->importance); | |
e729e793 JP |
373 | } else if (!strcmp(name, "cookie")) { |
374 | char *mask = strchr(value, '/'); | |
623e1caf | 375 | |
e729e793 | 376 | if (mask) { |
623e1caf | 377 | /* A mask means we're searching for a cookie. */ |
e729e793 | 378 | if (command == OFPFC_ADD) { |
bdda5aca BP |
379 | return xstrdup("flow additions cannot use " |
380 | "a cookie mask"); | |
e729e793 JP |
381 | } |
382 | *mask = '\0'; | |
bdda5aca BP |
383 | error = str_to_be64(value, &fm->cookie); |
384 | if (error) { | |
385 | return error; | |
386 | } | |
387 | error = str_to_be64(mask + 1, &fm->cookie_mask); | |
db0b6c29 JR |
388 | |
389 | /* Matching of the cookie is only supported through NXM or | |
390 | * OF1.1+. */ | |
391 | if (fm->cookie_mask != htonll(0)) { | |
392 | *usable_protocols &= OFPUTIL_P_NXM_OF11_UP; | |
393 | } | |
e729e793 | 394 | } else { |
623e1caf JP |
395 | /* No mask means that the cookie is being set. */ |
396 | if (command != OFPFC_ADD && command != OFPFC_MODIFY | |
bdda5aca BP |
397 | && command != OFPFC_MODIFY_STRICT) { |
398 | return xstrdup("cannot set cookie"); | |
623e1caf | 399 | } |
bdda5aca | 400 | error = str_to_be64(value, &fm->new_cookie); |
23342857 | 401 | fm->modify_cookie = true; |
e729e793 | 402 | } |
6a885fd0 | 403 | } else if (mf_from_name(name)) { |
db0b6c29 JR |
404 | error = parse_field(mf_from_name(name), value, &fm->match, |
405 | usable_protocols); | |
2c6d8411 BP |
406 | } else if (!strcmp(name, "duration") |
407 | || !strcmp(name, "n_packets") | |
146356e9 JP |
408 | || !strcmp(name, "n_bytes") |
409 | || !strcmp(name, "idle_age") | |
410 | || !strcmp(name, "hard_age")) { | |
2c6d8411 BP |
411 | /* Ignore these, so that users can feed the output of |
412 | * "ovs-ofctl dump-flows" back into commands that parse | |
413 | * flows. */ | |
f22716dc | 414 | } else { |
bdda5aca BP |
415 | error = xasprintf("unknown keyword %s", name); |
416 | } | |
417 | ||
418 | if (error) { | |
419 | return error; | |
f22716dc JP |
420 | } |
421 | } | |
422 | } | |
db0b6c29 JR |
423 | /* Check for usable protocol interdependencies between match fields. */ |
424 | if (fm->match.flow.dl_type == htons(ETH_TYPE_IPV6)) { | |
425 | const struct flow_wildcards *wc = &fm->match.wc; | |
426 | /* Only NXM and OXM support matching L3 and L4 fields within IPv6. | |
427 | * | |
428 | * (IPv6 specific fields as well as arp_sha, arp_tha, nw_frag, and | |
429 | * nw_ttl are covered elsewhere so they don't need to be included in | |
430 | * this test too.) | |
431 | */ | |
432 | if (wc->masks.nw_proto || wc->masks.nw_tos | |
433 | || wc->masks.tp_src || wc->masks.tp_dst) { | |
434 | *usable_protocols &= OFPUTIL_P_NXM_OXM_ANY; | |
435 | } | |
436 | } | |
b8266395 | 437 | if (!fm->cookie_mask && fm->new_cookie == OVS_BE64_MAX |
bdda5aca | 438 | && (command == OFPFC_MODIFY || command == OFPFC_MODIFY_STRICT)) { |
623e1caf JP |
439 | /* On modifies without a mask, we are supposed to add a flow if |
440 | * one does not exist. If a cookie wasn't been specified, use a | |
441 | * default of zero. */ | |
442 | fm->new_cookie = htonll(0); | |
443 | } | |
75a75043 | 444 | if (fields & F_ACTIONS) { |
c2d936a4 | 445 | enum ofputil_protocol action_usable_protocols; |
f25d0cf3 | 446 | struct ofpbuf ofpacts; |
bdda5aca | 447 | char *error; |
75a75043 | 448 | |
f25d0cf3 | 449 | ofpbuf_init(&ofpacts, 32); |
c2d936a4 BP |
450 | error = ofpacts_parse_instructions(act_str, &ofpacts, |
451 | &action_usable_protocols); | |
452 | *usable_protocols &= action_usable_protocols; | |
bdda5aca BP |
453 | if (!error) { |
454 | enum ofperr err; | |
455 | ||
1f317cb5 | 456 | err = ofpacts_check(ofpbuf_data(&ofpacts), ofpbuf_size(&ofpacts), &fm->match.flow, |
ba2fe8e9 BP |
457 | OFPP_MAX, fm->table_id, 255, usable_protocols); |
458 | if (!err && !usable_protocols) { | |
459 | err = OFPERR_OFPBAC_MATCH_INCONSISTENT; | |
460 | } | |
bdda5aca | 461 | if (err) { |
ba2fe8e9 BP |
462 | error = xasprintf("actions are invalid with specified match " |
463 | "(%s)", ofperr_to_string(err)); | |
bdda5aca | 464 | } |
ba2fe8e9 | 465 | |
bdda5aca BP |
466 | } |
467 | if (error) { | |
468 | ofpbuf_uninit(&ofpacts); | |
469 | return error; | |
b019d34d SH |
470 | } |
471 | ||
1f317cb5 | 472 | fm->ofpacts_len = ofpbuf_size(&ofpacts); |
bdda5aca | 473 | fm->ofpacts = ofpbuf_steal_data(&ofpacts); |
75a75043 | 474 | } else { |
f25d0cf3 BP |
475 | fm->ofpacts_len = 0; |
476 | fm->ofpacts = NULL; | |
75a75043 | 477 | } |
ec610b7b | 478 | |
bdda5aca | 479 | return NULL; |
f22716dc | 480 | } |
15f1f1b6 | 481 | |
638a19b0 | 482 | /* Convert 'str_' (as described in the Flow Syntax section of the ovs-ofctl man |
bdda5aca | 483 | * page) into 'fm' for sending the specified flow_mod 'command' to a switch. |
db0b6c29 | 484 | * Returns the set of usable protocols in '*usable_protocols'. |
bdda5aca BP |
485 | * |
486 | * To parse syntax for an OFPT_FLOW_MOD (or NXT_FLOW_MOD), use an OFPFC_* | |
487 | * constant for 'command'. To parse syntax for an OFPST_FLOW or | |
488 | * OFPST_AGGREGATE (or NXST_FLOW or NXST_AGGREGATE), use -1 for 'command'. | |
489 | * | |
490 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
491 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 492 | char * OVS_WARN_UNUSED_RESULT |
db0b6c29 | 493 | parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_, |
ba2fe8e9 | 494 | enum ofputil_protocol *usable_protocols) |
bdda5aca BP |
495 | { |
496 | char *string = xstrdup(str_); | |
497 | char *error; | |
498 | ||
ba2fe8e9 | 499 | error = parse_ofp_str__(fm, command, string, usable_protocols); |
bdda5aca BP |
500 | if (error) { |
501 | fm->ofpacts = NULL; | |
502 | fm->ofpacts_len = 0; | |
503 | } | |
504 | ||
505 | free(string); | |
506 | return error; | |
507 | } | |
508 | ||
cab50449 | 509 | static char * OVS_WARN_UNUSED_RESULT |
bdda5aca | 510 | parse_ofp_meter_mod_str__(struct ofputil_meter_mod *mm, char *string, |
db0b6c29 JR |
511 | struct ofpbuf *bands, int command, |
512 | enum ofputil_protocol *usable_protocols) | |
638a19b0 JR |
513 | { |
514 | enum { | |
515 | F_METER = 1 << 0, | |
516 | F_FLAGS = 1 << 1, | |
517 | F_BANDS = 1 << 2, | |
518 | } fields; | |
638a19b0 JR |
519 | char *save_ptr = NULL; |
520 | char *band_str = NULL; | |
521 | char *name; | |
522 | ||
db0b6c29 | 523 | /* Meters require at least OF 1.3. */ |
040f4db8 | 524 | *usable_protocols = OFPUTIL_P_OF13_UP; |
db0b6c29 | 525 | |
638a19b0 JR |
526 | switch (command) { |
527 | case -1: | |
528 | fields = F_METER; | |
529 | break; | |
530 | ||
531 | case OFPMC13_ADD: | |
532 | fields = F_METER | F_FLAGS | F_BANDS; | |
533 | break; | |
534 | ||
535 | case OFPMC13_DELETE: | |
536 | fields = F_METER; | |
537 | break; | |
538 | ||
539 | case OFPMC13_MODIFY: | |
540 | fields = F_METER | F_FLAGS | F_BANDS; | |
541 | break; | |
542 | ||
543 | default: | |
428b2edd | 544 | OVS_NOT_REACHED(); |
638a19b0 JR |
545 | } |
546 | ||
547 | mm->command = command; | |
548 | mm->meter.meter_id = 0; | |
549 | mm->meter.flags = 0; | |
550 | if (fields & F_BANDS) { | |
551 | band_str = strstr(string, "band"); | |
552 | if (!band_str) { | |
bdda5aca | 553 | return xstrdup("must specify bands"); |
638a19b0 JR |
554 | } |
555 | *band_str = '\0'; | |
556 | ||
557 | band_str = strchr(band_str + 1, '='); | |
558 | if (!band_str) { | |
bdda5aca | 559 | return xstrdup("must specify bands"); |
638a19b0 JR |
560 | } |
561 | ||
562 | band_str++; | |
563 | } | |
564 | for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; | |
565 | name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { | |
566 | ||
567 | if (fields & F_FLAGS && !strcmp(name, "kbps")) { | |
568 | mm->meter.flags |= OFPMF13_KBPS; | |
569 | } else if (fields & F_FLAGS && !strcmp(name, "pktps")) { | |
570 | mm->meter.flags |= OFPMF13_PKTPS; | |
571 | } else if (fields & F_FLAGS && !strcmp(name, "burst")) { | |
572 | mm->meter.flags |= OFPMF13_BURST; | |
573 | } else if (fields & F_FLAGS && !strcmp(name, "stats")) { | |
574 | mm->meter.flags |= OFPMF13_STATS; | |
575 | } else { | |
576 | char *value; | |
577 | ||
578 | value = strtok_r(NULL, ", \t\r\n", &save_ptr); | |
579 | if (!value) { | |
bdda5aca | 580 | return xasprintf("field %s missing value", name); |
638a19b0 JR |
581 | } |
582 | ||
583 | if (!strcmp(name, "meter")) { | |
584 | if (!strcmp(value, "all")) { | |
585 | mm->meter.meter_id = OFPM13_ALL; | |
586 | } else if (!strcmp(value, "controller")) { | |
587 | mm->meter.meter_id = OFPM13_CONTROLLER; | |
588 | } else if (!strcmp(value, "slowpath")) { | |
589 | mm->meter.meter_id = OFPM13_SLOWPATH; | |
590 | } else { | |
bdda5aca BP |
591 | char *error = str_to_u32(value, &mm->meter.meter_id); |
592 | if (error) { | |
593 | return error; | |
594 | } | |
dc8a858f JS |
595 | if (mm->meter.meter_id > OFPM13_MAX |
596 | || !mm->meter.meter_id) { | |
bdda5aca | 597 | return xasprintf("invalid value for %s", name); |
638a19b0 JR |
598 | } |
599 | } | |
600 | } else { | |
bdda5aca | 601 | return xasprintf("unknown keyword %s", name); |
638a19b0 JR |
602 | } |
603 | } | |
604 | } | |
605 | if (fields & F_METER && !mm->meter.meter_id) { | |
bdda5aca | 606 | return xstrdup("must specify 'meter'"); |
638a19b0 JR |
607 | } |
608 | if (fields & F_FLAGS && !mm->meter.flags) { | |
bdda5aca | 609 | return xstrdup("meter must specify either 'kbps' or 'pktps'"); |
638a19b0 JR |
610 | } |
611 | ||
612 | if (fields & F_BANDS) { | |
638a19b0 JR |
613 | uint16_t n_bands = 0; |
614 | struct ofputil_meter_band *band = NULL; | |
615 | int i; | |
616 | ||
638a19b0 JR |
617 | for (name = strtok_r(band_str, "=, \t\r\n", &save_ptr); name; |
618 | name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { | |
619 | ||
620 | char *value; | |
621 | ||
622 | value = strtok_r(NULL, ", \t\r\n", &save_ptr); | |
623 | if (!value) { | |
bdda5aca | 624 | return xasprintf("field %s missing value", name); |
638a19b0 JR |
625 | } |
626 | ||
627 | if (!strcmp(name, "type")) { | |
628 | /* Start a new band */ | |
bdda5aca | 629 | band = ofpbuf_put_zeros(bands, sizeof *band); |
638a19b0 JR |
630 | n_bands++; |
631 | ||
632 | if (!strcmp(value, "drop")) { | |
633 | band->type = OFPMBT13_DROP; | |
634 | } else if (!strcmp(value, "dscp_remark")) { | |
635 | band->type = OFPMBT13_DSCP_REMARK; | |
636 | } else { | |
bdda5aca | 637 | return xasprintf("field %s unknown value %s", name, value); |
638a19b0 JR |
638 | } |
639 | } else if (!band || !band->type) { | |
bdda5aca | 640 | return xstrdup("band must start with the 'type' keyword"); |
638a19b0 | 641 | } else if (!strcmp(name, "rate")) { |
bdda5aca BP |
642 | char *error = str_to_u32(value, &band->rate); |
643 | if (error) { | |
644 | return error; | |
645 | } | |
638a19b0 | 646 | } else if (!strcmp(name, "burst_size")) { |
bdda5aca BP |
647 | char *error = str_to_u32(value, &band->burst_size); |
648 | if (error) { | |
649 | return error; | |
650 | } | |
638a19b0 | 651 | } else if (!strcmp(name, "prec_level")) { |
bdda5aca BP |
652 | char *error = str_to_u8(value, name, &band->prec_level); |
653 | if (error) { | |
654 | return error; | |
655 | } | |
638a19b0 | 656 | } else { |
bdda5aca | 657 | return xasprintf("unknown keyword %s", name); |
638a19b0 JR |
658 | } |
659 | } | |
660 | /* validate bands */ | |
661 | if (!n_bands) { | |
bdda5aca | 662 | return xstrdup("meter must have bands"); |
638a19b0 JR |
663 | } |
664 | ||
665 | mm->meter.n_bands = n_bands; | |
bdda5aca | 666 | mm->meter.bands = ofpbuf_steal_data(bands); |
638a19b0 JR |
667 | |
668 | for (i = 0; i < n_bands; ++i) { | |
669 | band = &mm->meter.bands[i]; | |
670 | ||
671 | if (!band->type) { | |
bdda5aca | 672 | return xstrdup("band must have 'type'"); |
638a19b0 JR |
673 | } |
674 | if (band->type == OFPMBT13_DSCP_REMARK) { | |
675 | if (!band->prec_level) { | |
bdda5aca BP |
676 | return xstrdup("'dscp_remark' band must have" |
677 | " 'prec_level'"); | |
638a19b0 JR |
678 | } |
679 | } else { | |
680 | if (band->prec_level) { | |
bdda5aca BP |
681 | return xstrdup("Only 'dscp_remark' band may have" |
682 | " 'prec_level'"); | |
638a19b0 JR |
683 | } |
684 | } | |
685 | if (!band->rate) { | |
bdda5aca | 686 | return xstrdup("band must have 'rate'"); |
638a19b0 JR |
687 | } |
688 | if (mm->meter.flags & OFPMF13_BURST) { | |
689 | if (!band->burst_size) { | |
bdda5aca BP |
690 | return xstrdup("band must have 'burst_size' " |
691 | "when 'burst' flag is set"); | |
638a19b0 JR |
692 | } |
693 | } else { | |
694 | if (band->burst_size) { | |
bdda5aca BP |
695 | return xstrdup("band may have 'burst_size' only " |
696 | "when 'burst' flag is set"); | |
638a19b0 JR |
697 | } |
698 | } | |
699 | } | |
700 | } else { | |
701 | mm->meter.n_bands = 0; | |
702 | mm->meter.bands = NULL; | |
703 | } | |
704 | ||
bdda5aca BP |
705 | return NULL; |
706 | } | |
707 | ||
708 | /* Convert 'str_' (as described in the Flow Syntax section of the ovs-ofctl man | |
709 | * page) into 'mm' for sending the specified meter_mod 'command' to a switch. | |
710 | * | |
711 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
712 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 713 | char * OVS_WARN_UNUSED_RESULT |
bdda5aca | 714 | parse_ofp_meter_mod_str(struct ofputil_meter_mod *mm, const char *str_, |
db0b6c29 | 715 | int command, enum ofputil_protocol *usable_protocols) |
bdda5aca BP |
716 | { |
717 | struct ofpbuf bands; | |
718 | char *string; | |
719 | char *error; | |
720 | ||
721 | ofpbuf_init(&bands, 64); | |
722 | string = xstrdup(str_); | |
723 | ||
db0b6c29 JR |
724 | error = parse_ofp_meter_mod_str__(mm, string, &bands, command, |
725 | usable_protocols); | |
bdda5aca | 726 | |
638a19b0 | 727 | free(string); |
bdda5aca BP |
728 | ofpbuf_uninit(&bands); |
729 | ||
730 | return error; | |
638a19b0 JR |
731 | } |
732 | ||
cab50449 | 733 | static char * OVS_WARN_UNUSED_RESULT |
bdda5aca | 734 | parse_flow_monitor_request__(struct ofputil_flow_monitor_request *fmr, |
db0b6c29 JR |
735 | const char *str_, char *string, |
736 | enum ofputil_protocol *usable_protocols) | |
2b07c8b1 | 737 | { |
ca4fbdfe | 738 | static atomic_count id = ATOMIC_COUNT_INIT(0); |
2b07c8b1 BP |
739 | char *save_ptr = NULL; |
740 | char *name; | |
741 | ||
ca4fbdfe | 742 | fmr->id = atomic_count_inc(&id); |
a944ef40 | 743 | |
2b07c8b1 BP |
744 | fmr->flags = (NXFMF_INITIAL | NXFMF_ADD | NXFMF_DELETE | NXFMF_MODIFY |
745 | | NXFMF_OWN | NXFMF_ACTIONS); | |
746 | fmr->out_port = OFPP_NONE; | |
747 | fmr->table_id = 0xff; | |
81a76618 | 748 | match_init_catchall(&fmr->match); |
2b07c8b1 BP |
749 | |
750 | for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; | |
751 | name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { | |
752 | const struct protocol *p; | |
753 | ||
754 | if (!strcmp(name, "!initial")) { | |
755 | fmr->flags &= ~NXFMF_INITIAL; | |
756 | } else if (!strcmp(name, "!add")) { | |
757 | fmr->flags &= ~NXFMF_ADD; | |
758 | } else if (!strcmp(name, "!delete")) { | |
759 | fmr->flags &= ~NXFMF_DELETE; | |
760 | } else if (!strcmp(name, "!modify")) { | |
761 | fmr->flags &= ~NXFMF_MODIFY; | |
762 | } else if (!strcmp(name, "!actions")) { | |
763 | fmr->flags &= ~NXFMF_ACTIONS; | |
764 | } else if (!strcmp(name, "!own")) { | |
765 | fmr->flags &= ~NXFMF_OWN; | |
766 | } else if (parse_protocol(name, &p)) { | |
81a76618 | 767 | match_set_dl_type(&fmr->match, htons(p->dl_type)); |
2b07c8b1 | 768 | if (p->nw_proto) { |
81a76618 | 769 | match_set_nw_proto(&fmr->match, p->nw_proto); |
2b07c8b1 BP |
770 | } |
771 | } else { | |
772 | char *value; | |
773 | ||
774 | value = strtok_r(NULL, ", \t\r\n", &save_ptr); | |
775 | if (!value) { | |
bdda5aca | 776 | return xasprintf("%s: field %s missing value", str_, name); |
2b07c8b1 BP |
777 | } |
778 | ||
779 | if (!strcmp(name, "table")) { | |
bdda5aca BP |
780 | char *error = str_to_u8(value, "table", &fmr->table_id); |
781 | if (error) { | |
782 | return error; | |
783 | } | |
2b07c8b1 | 784 | } else if (!strcmp(name, "out_port")) { |
4e022ec0 | 785 | fmr->out_port = u16_to_ofp(atoi(value)); |
2b07c8b1 | 786 | } else if (mf_from_name(name)) { |
bdda5aca BP |
787 | char *error; |
788 | ||
db0b6c29 JR |
789 | error = parse_field(mf_from_name(name), value, &fmr->match, |
790 | usable_protocols); | |
bdda5aca BP |
791 | if (error) { |
792 | return error; | |
793 | } | |
2b07c8b1 | 794 | } else { |
bdda5aca | 795 | return xasprintf("%s: unknown keyword %s", str_, name); |
2b07c8b1 BP |
796 | } |
797 | } | |
798 | } | |
bdda5aca BP |
799 | return NULL; |
800 | } | |
801 | ||
802 | /* Convert 'str_' (as described in the documentation for the "monitor" command | |
803 | * in the ovs-ofctl man page) into 'fmr'. | |
804 | * | |
805 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
806 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 807 | char * OVS_WARN_UNUSED_RESULT |
bdda5aca | 808 | parse_flow_monitor_request(struct ofputil_flow_monitor_request *fmr, |
db0b6c29 JR |
809 | const char *str_, |
810 | enum ofputil_protocol *usable_protocols) | |
bdda5aca BP |
811 | { |
812 | char *string = xstrdup(str_); | |
db0b6c29 JR |
813 | char *error = parse_flow_monitor_request__(fmr, str_, string, |
814 | usable_protocols); | |
2b07c8b1 | 815 | free(string); |
bdda5aca | 816 | return error; |
2b07c8b1 BP |
817 | } |
818 | ||
88ca35ee | 819 | /* Parses 'string' as an OFPT_FLOW_MOD or NXT_FLOW_MOD with command 'command' |
bdda5aca BP |
820 | * (one of OFPFC_*) into 'fm'. |
821 | * | |
822 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
823 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 824 | char * OVS_WARN_UNUSED_RESULT |
27527aa0 | 825 | parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm, const char *string, |
db0b6c29 | 826 | uint16_t command, |
ba2fe8e9 | 827 | enum ofputil_protocol *usable_protocols) |
15f1f1b6 | 828 | { |
ba2fe8e9 | 829 | char *error = parse_ofp_str(fm, command, string, usable_protocols); |
bdda5aca BP |
830 | if (!error) { |
831 | /* Normalize a copy of the match. This ensures that non-normalized | |
832 | * flows get logged but doesn't affect what gets sent to the switch, so | |
833 | * that the switch can do whatever it likes with the flow. */ | |
834 | struct match match_copy = fm->match; | |
835 | ofputil_normalize_match(&match_copy); | |
836 | } | |
88ca35ee | 837 | |
bdda5aca | 838 | return error; |
15f1f1b6 BP |
839 | } |
840 | ||
918f2b82 AZ |
841 | /* Convert 'table_id' and 'flow_miss_handling' (as described for the |
842 | * "mod-table" command in the ovs-ofctl man page) into 'tm' for sending the | |
843 | * specified table_mod 'command' to a switch. | |
844 | * | |
845 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
846 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 847 | char * OVS_WARN_UNUSED_RESULT |
918f2b82 AZ |
848 | parse_ofp_table_mod(struct ofputil_table_mod *tm, const char *table_id, |
849 | const char *flow_miss_handling, | |
850 | enum ofputil_protocol *usable_protocols) | |
851 | { | |
852 | /* Table mod requires at least OF 1.1. */ | |
853 | *usable_protocols = OFPUTIL_P_OF11_UP; | |
854 | ||
855 | if (!strcasecmp(table_id, "all")) { | |
083761ad | 856 | tm->table_id = OFPTT_ALL; |
918f2b82 AZ |
857 | } else { |
858 | char *error = str_to_u8(table_id, "table_id", &tm->table_id); | |
859 | if (error) { | |
860 | return error; | |
861 | } | |
862 | } | |
863 | ||
864 | if (strcmp(flow_miss_handling, "controller") == 0) { | |
3c1bb396 | 865 | tm->miss_config = OFPUTIL_TABLE_MISS_CONTROLLER; |
918f2b82 | 866 | } else if (strcmp(flow_miss_handling, "continue") == 0) { |
3c1bb396 | 867 | tm->miss_config = OFPUTIL_TABLE_MISS_CONTINUE; |
918f2b82 | 868 | } else if (strcmp(flow_miss_handling, "drop") == 0) { |
3c1bb396 | 869 | tm->miss_config = OFPUTIL_TABLE_MISS_DROP; |
918f2b82 AZ |
870 | } else { |
871 | return xasprintf("invalid flow_miss_handling %s", flow_miss_handling); | |
872 | } | |
873 | ||
3c1bb396 BP |
874 | if (tm->table_id == 0xfe |
875 | && tm->miss_config == OFPUTIL_TABLE_MISS_CONTINUE) { | |
918f2b82 AZ |
876 | return xstrdup("last table's flow miss handling can not be continue"); |
877 | } | |
878 | ||
879 | return NULL; | |
880 | } | |
881 | ||
882 | ||
bdda5aca BP |
883 | /* Opens file 'file_name' and reads each line as a flow_mod of the specified |
884 | * type (one of OFPFC_*). Stores each flow_mod in '*fm', an array allocated | |
885 | * on the caller's behalf, and the number of flow_mods in '*n_fms'. | |
886 | * | |
887 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
888 | * error. The caller is responsible for freeing the returned string. */ | |
cab50449 | 889 | char * OVS_WARN_UNUSED_RESULT |
27527aa0 | 890 | parse_ofp_flow_mod_file(const char *file_name, uint16_t command, |
db0b6c29 | 891 | struct ofputil_flow_mod **fms, size_t *n_fms, |
ba2fe8e9 | 892 | enum ofputil_protocol *usable_protocols) |
15f1f1b6 | 893 | { |
27527aa0 | 894 | size_t allocated_fms; |
bdda5aca | 895 | int line_number; |
27527aa0 | 896 | FILE *stream; |
dd8101bc | 897 | struct ds s; |
15f1f1b6 | 898 | |
db0b6c29 JR |
899 | *usable_protocols = OFPUTIL_P_ANY; |
900 | ||
bdda5aca BP |
901 | *fms = NULL; |
902 | *n_fms = 0; | |
903 | ||
27527aa0 BP |
904 | stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r"); |
905 | if (stream == NULL) { | |
bdda5aca BP |
906 | return xasprintf("%s: open failed (%s)", |
907 | file_name, ovs_strerror(errno)); | |
27527aa0 BP |
908 | } |
909 | ||
910 | allocated_fms = *n_fms; | |
dd8101bc | 911 | ds_init(&s); |
bdda5aca BP |
912 | line_number = 0; |
913 | while (!ds_get_preprocessed_line(&s, stream, &line_number)) { | |
914 | char *error; | |
db0b6c29 | 915 | enum ofputil_protocol usable; |
bdda5aca | 916 | |
27527aa0 BP |
917 | if (*n_fms >= allocated_fms) { |
918 | *fms = x2nrealloc(*fms, &allocated_fms, sizeof **fms); | |
919 | } | |
db0b6c29 | 920 | error = parse_ofp_flow_mod_str(&(*fms)[*n_fms], ds_cstr(&s), command, |
ba2fe8e9 | 921 | &usable); |
bdda5aca BP |
922 | if (error) { |
923 | size_t i; | |
924 | ||
925 | for (i = 0; i < *n_fms; i++) { | |
dc723c44 | 926 | free(CONST_CAST(struct ofpact *, (*fms)[i].ofpacts)); |
bdda5aca BP |
927 | } |
928 | free(*fms); | |
929 | *fms = NULL; | |
930 | *n_fms = 0; | |
931 | ||
932 | ds_destroy(&s); | |
933 | if (stream != stdin) { | |
934 | fclose(stream); | |
935 | } | |
936 | ||
937 | return xasprintf("%s:%d: %s", file_name, line_number, error); | |
938 | } | |
db0b6c29 | 939 | *usable_protocols &= usable; /* Each line can narrow the set. */ |
27527aa0 | 940 | *n_fms += 1; |
15f1f1b6 | 941 | } |
15f1f1b6 | 942 | |
bdda5aca | 943 | ds_destroy(&s); |
27527aa0 BP |
944 | if (stream != stdin) { |
945 | fclose(stream); | |
946 | } | |
bdda5aca | 947 | return NULL; |
88ca35ee BP |
948 | } |
949 | ||
cab50449 | 950 | char * OVS_WARN_UNUSED_RESULT |
81d1ea94 | 951 | parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr, |
db0b6c29 | 952 | bool aggregate, const char *string, |
ba2fe8e9 | 953 | enum ofputil_protocol *usable_protocols) |
88ca35ee | 954 | { |
a9a2da38 | 955 | struct ofputil_flow_mod fm; |
bdda5aca BP |
956 | char *error; |
957 | ||
ba2fe8e9 | 958 | error = parse_ofp_str(&fm, -1, string, usable_protocols); |
bdda5aca BP |
959 | if (error) { |
960 | return error; | |
961 | } | |
88ca35ee | 962 | |
db0b6c29 JR |
963 | /* Special table ID support not required for stats requests. */ |
964 | if (*usable_protocols & OFPUTIL_P_OF10_STD_TID) { | |
965 | *usable_protocols |= OFPUTIL_P_OF10_STD; | |
966 | } | |
967 | if (*usable_protocols & OFPUTIL_P_OF10_NXM_TID) { | |
968 | *usable_protocols |= OFPUTIL_P_OF10_NXM; | |
969 | } | |
970 | ||
88ca35ee | 971 | fsr->aggregate = aggregate; |
e729e793 JP |
972 | fsr->cookie = fm.cookie; |
973 | fsr->cookie_mask = fm.cookie_mask; | |
81a76618 | 974 | fsr->match = fm.match; |
88ca35ee | 975 | fsr->out_port = fm.out_port; |
7395c052 | 976 | fsr->out_group = fm.out_group; |
6c1491fb | 977 | fsr->table_id = fm.table_id; |
bdda5aca | 978 | return NULL; |
15f1f1b6 | 979 | } |
ccbe50f8 BP |
980 | |
981 | /* Parses a specification of a flow from 's' into 'flow'. 's' must take the | |
982 | * form FIELD=VALUE[,FIELD=VALUE]... where each FIELD is the name of a | |
983 | * mf_field. Fields must be specified in a natural order for satisfying | |
5a0a5702 GS |
984 | * prerequisites. If 'mask' is specified, fills the mask field for each of the |
985 | * field specified in flow. If the map, 'names_portno' is specfied, converts | |
986 | * the in_port name into port no while setting the 'flow'. | |
ccbe50f8 BP |
987 | * |
988 | * Returns NULL on success, otherwise a malloc()'d string that explains the | |
989 | * problem. */ | |
990 | char * | |
5a0a5702 GS |
991 | parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s, |
992 | const struct simap *portno_names) | |
ccbe50f8 BP |
993 | { |
994 | char *pos, *key, *value_s; | |
995 | char *error = NULL; | |
996 | char *copy; | |
997 | ||
998 | memset(flow, 0, sizeof *flow); | |
5a0a5702 GS |
999 | if (mask) { |
1000 | memset(mask, 0, sizeof *mask); | |
1001 | } | |
ccbe50f8 BP |
1002 | |
1003 | pos = copy = xstrdup(s); | |
1004 | while (ofputil_parse_key_value(&pos, &key, &value_s)) { | |
1005 | const struct protocol *p; | |
1006 | if (parse_protocol(key, &p)) { | |
1007 | if (flow->dl_type) { | |
1008 | error = xasprintf("%s: Ethernet type set multiple times", s); | |
1009 | goto exit; | |
1010 | } | |
1011 | flow->dl_type = htons(p->dl_type); | |
5a0a5702 GS |
1012 | if (mask) { |
1013 | mask->dl_type = OVS_BE16_MAX; | |
1014 | } | |
ccbe50f8 BP |
1015 | |
1016 | if (p->nw_proto) { | |
1017 | if (flow->nw_proto) { | |
1018 | error = xasprintf("%s: network protocol set " | |
1019 | "multiple times", s); | |
1020 | goto exit; | |
1021 | } | |
1022 | flow->nw_proto = p->nw_proto; | |
5a0a5702 GS |
1023 | if (mask) { |
1024 | mask->nw_proto = UINT8_MAX; | |
1025 | } | |
ccbe50f8 BP |
1026 | } |
1027 | } else { | |
1028 | const struct mf_field *mf; | |
1029 | union mf_value value; | |
1030 | char *field_error; | |
1031 | ||
1032 | mf = mf_from_name(key); | |
1033 | if (!mf) { | |
1034 | error = xasprintf("%s: unknown field %s", s, key); | |
1035 | goto exit; | |
1036 | } | |
1037 | ||
1038 | if (!mf_are_prereqs_ok(mf, flow)) { | |
1039 | error = xasprintf("%s: prerequisites not met for setting %s", | |
1040 | s, key); | |
1041 | goto exit; | |
1042 | } | |
1043 | ||
1044 | if (!mf_is_zero(mf, flow)) { | |
1045 | error = xasprintf("%s: field %s set multiple times", s, key); | |
1046 | goto exit; | |
1047 | } | |
1048 | ||
5a0a5702 GS |
1049 | if (!strcmp(key, "in_port") |
1050 | && portno_names | |
1051 | && simap_contains(portno_names, value_s)) { | |
1052 | flow->in_port.ofp_port = u16_to_ofp( | |
1053 | simap_get(portno_names, value_s)); | |
1054 | if (mask) { | |
1055 | mask->in_port.ofp_port = u16_to_ofp(ntohs(OVS_BE16_MAX)); | |
1056 | } | |
1057 | } else { | |
1058 | field_error = mf_parse_value(mf, value_s, &value); | |
1059 | if (field_error) { | |
1060 | error = xasprintf("%s: bad value for %s (%s)", | |
1061 | s, key, field_error); | |
1062 | free(field_error); | |
1063 | goto exit; | |
1064 | } | |
ccbe50f8 | 1065 | |
5a0a5702 GS |
1066 | mf_set_flow_value(mf, &value, flow); |
1067 | if (mask) { | |
1068 | mf_mask_field(mf, mask); | |
1069 | } | |
1070 | } | |
ccbe50f8 BP |
1071 | } |
1072 | } | |
1073 | ||
4e022ec0 AW |
1074 | if (!flow->in_port.ofp_port) { |
1075 | flow->in_port.ofp_port = OFPP_NONE; | |
72d64e33 EJ |
1076 | } |
1077 | ||
ccbe50f8 BP |
1078 | exit: |
1079 | free(copy); | |
1080 | ||
1081 | if (error) { | |
1082 | memset(flow, 0, sizeof *flow); | |
5a0a5702 GS |
1083 | if (mask) { |
1084 | memset(mask, 0, sizeof *mask); | |
1085 | } | |
ccbe50f8 BP |
1086 | } |
1087 | return error; | |
1088 | } | |
7395c052 | 1089 | |
cab50449 | 1090 | static char * OVS_WARN_UNUSED_RESULT |
7395c052 NZ |
1091 | parse_bucket_str(struct ofputil_bucket *bucket, char *str_, |
1092 | enum ofputil_protocol *usable_protocols) | |
1093 | { | |
c2d936a4 | 1094 | char *pos, *key, *value; |
7395c052 | 1095 | struct ofpbuf ofpacts; |
c2d936a4 BP |
1096 | struct ds actions; |
1097 | char *error; | |
7395c052 NZ |
1098 | |
1099 | bucket->weight = 1; | |
18ac06d3 | 1100 | bucket->bucket_id = OFPG15_BUCKET_ALL; |
7395c052 NZ |
1101 | bucket->watch_port = OFPP_ANY; |
1102 | bucket->watch_group = OFPG11_ANY; | |
1103 | ||
c2d936a4 | 1104 | ds_init(&actions); |
7395c052 | 1105 | |
c2d936a4 BP |
1106 | pos = str_; |
1107 | error = NULL; | |
1108 | while (ofputil_parse_key_value(&pos, &key, &value)) { | |
1109 | if (!strcasecmp(key, "weight")) { | |
1110 | error = str_to_u16(value, "weight", &bucket->weight); | |
1111 | } else if (!strcasecmp(key, "watch_port")) { | |
1112 | if (!ofputil_port_from_string(value, &bucket->watch_port) | |
7395c052 NZ |
1113 | || (ofp_to_u16(bucket->watch_port) >= ofp_to_u16(OFPP_MAX) |
1114 | && bucket->watch_port != OFPP_ANY)) { | |
c2d936a4 | 1115 | error = xasprintf("%s: invalid watch_port", value); |
7395c052 | 1116 | } |
c2d936a4 BP |
1117 | } else if (!strcasecmp(key, "watch_group")) { |
1118 | error = str_to_u32(value, &bucket->watch_group); | |
7395c052 NZ |
1119 | if (!error && bucket->watch_group > OFPG_MAX) { |
1120 | error = xasprintf("invalid watch_group id %"PRIu32, | |
1121 | bucket->watch_group); | |
1122 | } | |
2d5d050c SH |
1123 | } else if (!strcasecmp(key, "bucket_id")) { |
1124 | error = str_to_u32(value, &bucket->bucket_id); | |
1125 | if (!error && bucket->bucket_id > OFPG15_BUCKET_MAX) { | |
1126 | error = xasprintf("invalid bucket_id id %"PRIu32, | |
1127 | bucket->bucket_id); | |
1128 | } | |
1129 | *usable_protocols &= OFPUTIL_P_OF15_UP; | |
c2d936a4 BP |
1130 | } else if (!strcasecmp(key, "action") || !strcasecmp(key, "actions")) { |
1131 | ds_put_format(&actions, "%s,", value); | |
7395c052 | 1132 | } else { |
c2d936a4 | 1133 | ds_put_format(&actions, "%s(%s),", key, value); |
7395c052 NZ |
1134 | } |
1135 | ||
1136 | if (error) { | |
c2d936a4 | 1137 | ds_destroy(&actions); |
7395c052 NZ |
1138 | return error; |
1139 | } | |
1140 | } | |
1141 | ||
c2d936a4 BP |
1142 | if (!actions.length) { |
1143 | return xstrdup("bucket must specify actions"); | |
1144 | } | |
1145 | ds_chomp(&actions, ','); | |
1146 | ||
1147 | ofpbuf_init(&ofpacts, 0); | |
1148 | error = ofpacts_parse_actions(ds_cstr(&actions), &ofpacts, | |
1149 | usable_protocols); | |
1150 | ds_destroy(&actions); | |
1151 | if (error) { | |
1152 | ofpbuf_uninit(&ofpacts); | |
1153 | return error; | |
1154 | } | |
1f317cb5 PS |
1155 | bucket->ofpacts = ofpbuf_data(&ofpacts); |
1156 | bucket->ofpacts_len = ofpbuf_size(&ofpacts); | |
7395c052 NZ |
1157 | |
1158 | return NULL; | |
1159 | } | |
1160 | ||
cab50449 | 1161 | static char * OVS_WARN_UNUSED_RESULT |
7395c052 NZ |
1162 | parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command, |
1163 | char *string, | |
1164 | enum ofputil_protocol *usable_protocols) | |
1165 | { | |
1166 | enum { | |
45952551 SH |
1167 | F_GROUP_TYPE = 1 << 0, |
1168 | F_BUCKETS = 1 << 1, | |
1169 | F_COMMAND_BUCKET_ID = 1 << 2, | |
1170 | F_COMMAND_BUCKET_ID_ALL = 1 << 3, | |
7395c052 NZ |
1171 | } fields; |
1172 | char *save_ptr = NULL; | |
1173 | bool had_type = false; | |
45952551 | 1174 | bool had_command_bucket_id = false; |
7395c052 NZ |
1175 | char *name; |
1176 | struct ofputil_bucket *bucket; | |
1177 | char *error = NULL; | |
1178 | ||
1179 | *usable_protocols = OFPUTIL_P_OF11_UP; | |
1180 | ||
1181 | switch (command) { | |
1182 | case OFPGC11_ADD: | |
1183 | fields = F_GROUP_TYPE | F_BUCKETS; | |
1184 | break; | |
1185 | ||
1186 | case OFPGC11_DELETE: | |
1187 | fields = 0; | |
1188 | break; | |
1189 | ||
1190 | case OFPGC11_MODIFY: | |
1191 | fields = F_GROUP_TYPE | F_BUCKETS; | |
1192 | break; | |
1193 | ||
45952551 SH |
1194 | case OFPGC15_INSERT_BUCKET: |
1195 | fields = F_BUCKETS | F_COMMAND_BUCKET_ID; | |
1196 | *usable_protocols &= OFPUTIL_P_OF15_UP; | |
1197 | break; | |
1198 | ||
1199 | case OFPGC15_REMOVE_BUCKET: | |
1200 | fields = F_COMMAND_BUCKET_ID | F_COMMAND_BUCKET_ID_ALL; | |
1201 | *usable_protocols &= OFPUTIL_P_OF15_UP; | |
1202 | break; | |
1203 | ||
7395c052 | 1204 | default: |
428b2edd | 1205 | OVS_NOT_REACHED(); |
7395c052 NZ |
1206 | } |
1207 | ||
1208 | memset(gm, 0, sizeof *gm); | |
1209 | gm->command = command; | |
1210 | gm->group_id = OFPG_ANY; | |
18ac06d3 | 1211 | gm->command_bucket_id = OFPG15_BUCKET_ALL; |
7395c052 NZ |
1212 | list_init(&gm->buckets); |
1213 | if (command == OFPGC11_DELETE && string[0] == '\0') { | |
1214 | gm->group_id = OFPG_ALL; | |
1215 | return NULL; | |
1216 | } | |
1217 | ||
1218 | *usable_protocols = OFPUTIL_P_OF11_UP; | |
1219 | ||
1220 | if (fields & F_BUCKETS) { | |
2d5d050c | 1221 | char *bkt_str = strstr(string, "bucket="); |
7395c052 NZ |
1222 | |
1223 | if (bkt_str) { | |
1224 | *bkt_str = '\0'; | |
1225 | } | |
1226 | ||
1227 | while (bkt_str) { | |
1228 | char *next_bkt_str; | |
1229 | ||
1230 | bkt_str = strchr(bkt_str + 1, '='); | |
1231 | if (!bkt_str) { | |
1232 | error = xstrdup("must specify bucket content"); | |
1233 | goto out; | |
1234 | } | |
1235 | bkt_str++; | |
1236 | ||
2d5d050c | 1237 | next_bkt_str = strstr(bkt_str, "bucket="); |
7395c052 NZ |
1238 | if (next_bkt_str) { |
1239 | *next_bkt_str = '\0'; | |
1240 | } | |
1241 | ||
1242 | bucket = xzalloc(sizeof(struct ofputil_bucket)); | |
1243 | error = parse_bucket_str(bucket, bkt_str, usable_protocols); | |
1244 | if (error) { | |
1245 | free(bucket); | |
1246 | goto out; | |
1247 | } | |
1248 | list_push_back(&gm->buckets, &bucket->list_node); | |
1249 | ||
1250 | bkt_str = next_bkt_str; | |
1251 | } | |
1252 | } | |
1253 | ||
1254 | for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; | |
1255 | name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { | |
1256 | char *value; | |
1257 | ||
1258 | value = strtok_r(NULL, ", \t\r\n", &save_ptr); | |
1259 | if (!value) { | |
1260 | error = xasprintf("field %s missing value", name); | |
1261 | goto out; | |
1262 | } | |
1263 | ||
45952551 SH |
1264 | if (!strcmp(name, "command_bucket_id")) { |
1265 | if (!(fields & F_COMMAND_BUCKET_ID)) { | |
1266 | error = xstrdup("command bucket id is not needed"); | |
1267 | goto out; | |
1268 | } | |
1269 | if (!strcmp(value, "all")) { | |
1270 | gm->command_bucket_id = OFPG15_BUCKET_ALL; | |
1271 | } else if (!strcmp(value, "first")) { | |
1272 | gm->command_bucket_id = OFPG15_BUCKET_FIRST; | |
1273 | } else if (!strcmp(value, "last")) { | |
1274 | gm->command_bucket_id = OFPG15_BUCKET_LAST; | |
1275 | } else { | |
1276 | char *error = str_to_u32(value, &gm->command_bucket_id); | |
1277 | if (error) { | |
1278 | goto out; | |
1279 | } | |
1280 | if (gm->command_bucket_id > OFPG15_BUCKET_MAX | |
1281 | && (gm->command_bucket_id != OFPG15_BUCKET_FIRST | |
1282 | && gm->command_bucket_id != OFPG15_BUCKET_LAST | |
1283 | && gm->command_bucket_id != OFPG15_BUCKET_ALL)) { | |
1284 | error = xasprintf("invalid command bucket id %"PRIu32, | |
1285 | gm->command_bucket_id); | |
1286 | goto out; | |
1287 | } | |
1288 | } | |
1289 | if (gm->command_bucket_id == OFPG15_BUCKET_ALL | |
1290 | && !(fields & F_COMMAND_BUCKET_ID_ALL)) { | |
1291 | error = xstrdup("command_bucket_id=all is not permitted"); | |
1292 | goto out; | |
1293 | } | |
1294 | had_command_bucket_id = true; | |
1295 | } else if (!strcmp(name, "group_id")) { | |
7395c052 NZ |
1296 | if(!strcmp(value, "all")) { |
1297 | gm->group_id = OFPG_ALL; | |
1298 | } else { | |
1299 | char *error = str_to_u32(value, &gm->group_id); | |
1300 | if (error) { | |
1301 | goto out; | |
1302 | } | |
1303 | if (gm->group_id != OFPG_ALL && gm->group_id > OFPG_MAX) { | |
1304 | error = xasprintf("invalid group id %"PRIu32, | |
1305 | gm->group_id); | |
1306 | goto out; | |
1307 | } | |
1308 | } | |
1309 | } else if (!strcmp(name, "type")){ | |
1310 | if (!(fields & F_GROUP_TYPE)) { | |
1311 | error = xstrdup("type is not needed"); | |
1312 | goto out; | |
1313 | } | |
1314 | if (!strcmp(value, "all")) { | |
1315 | gm->type = OFPGT11_ALL; | |
1316 | } else if (!strcmp(value, "select")) { | |
1317 | gm->type = OFPGT11_SELECT; | |
1318 | } else if (!strcmp(value, "indirect")) { | |
1319 | gm->type = OFPGT11_INDIRECT; | |
1320 | } else if (!strcmp(value, "ff") || | |
1321 | !strcmp(value, "fast_failover")) { | |
1322 | gm->type = OFPGT11_FF; | |
1323 | } else { | |
1324 | error = xasprintf("invalid group type %s", value); | |
1325 | goto out; | |
1326 | } | |
1327 | had_type = true; | |
1328 | } else if (!strcmp(name, "bucket")) { | |
1329 | error = xstrdup("bucket is not needed"); | |
1330 | goto out; | |
1331 | } else { | |
1332 | error = xasprintf("unknown keyword %s", name); | |
1333 | goto out; | |
1334 | } | |
1335 | } | |
1336 | if (gm->group_id == OFPG_ANY) { | |
1337 | error = xstrdup("must specify a group_id"); | |
1338 | goto out; | |
1339 | } | |
1340 | if (fields & F_GROUP_TYPE && !had_type) { | |
1341 | error = xstrdup("must specify a type"); | |
1342 | goto out; | |
1343 | } | |
1344 | ||
45952551 SH |
1345 | if (fields & F_COMMAND_BUCKET_ID) { |
1346 | if (!(fields & F_COMMAND_BUCKET_ID_ALL || had_command_bucket_id)) { | |
1347 | error = xstrdup("must specify a command bucket id"); | |
1348 | goto out; | |
1349 | } | |
1350 | } else if (had_command_bucket_id) { | |
1351 | error = xstrdup("command bucket id is not needed"); | |
1352 | goto out; | |
1353 | } | |
1354 | ||
7395c052 NZ |
1355 | /* Validate buckets. */ |
1356 | LIST_FOR_EACH (bucket, list_node, &gm->buckets) { | |
1357 | if (bucket->weight != 1 && gm->type != OFPGT11_SELECT) { | |
1358 | error = xstrdup("Only select groups can have bucket weights."); | |
1359 | goto out; | |
1360 | } | |
1361 | } | |
1362 | if (gm->type == OFPGT11_INDIRECT && !list_is_short(&gm->buckets)) { | |
1363 | error = xstrdup("Indirect groups can have at most one bucket."); | |
1364 | goto out; | |
1365 | } | |
1366 | ||
1367 | return NULL; | |
1368 | out: | |
1369 | ofputil_bucket_list_destroy(&gm->buckets); | |
1370 | return error; | |
1371 | } | |
1372 | ||
cab50449 | 1373 | char * OVS_WARN_UNUSED_RESULT |
7395c052 NZ |
1374 | parse_ofp_group_mod_str(struct ofputil_group_mod *gm, uint16_t command, |
1375 | const char *str_, | |
1376 | enum ofputil_protocol *usable_protocols) | |
1377 | { | |
1378 | char *string = xstrdup(str_); | |
1379 | char *error = parse_ofp_group_mod_str__(gm, command, string, | |
1380 | usable_protocols); | |
1381 | free(string); | |
1382 | ||
1383 | if (error) { | |
1384 | ofputil_bucket_list_destroy(&gm->buckets); | |
1385 | } | |
1386 | return error; | |
1387 | } | |
1388 | ||
cab50449 | 1389 | char * OVS_WARN_UNUSED_RESULT |
7395c052 NZ |
1390 | parse_ofp_group_mod_file(const char *file_name, uint16_t command, |
1391 | struct ofputil_group_mod **gms, size_t *n_gms, | |
1392 | enum ofputil_protocol *usable_protocols) | |
1393 | { | |
1394 | size_t allocated_gms; | |
1395 | int line_number; | |
1396 | FILE *stream; | |
1397 | struct ds s; | |
1398 | ||
1399 | *gms = NULL; | |
1400 | *n_gms = 0; | |
1401 | ||
1402 | stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r"); | |
1403 | if (stream == NULL) { | |
1404 | return xasprintf("%s: open failed (%s)", | |
1405 | file_name, ovs_strerror(errno)); | |
1406 | } | |
1407 | ||
1408 | allocated_gms = *n_gms; | |
1409 | ds_init(&s); | |
1410 | line_number = 0; | |
1411 | *usable_protocols = OFPUTIL_P_OF11_UP; | |
1412 | while (!ds_get_preprocessed_line(&s, stream, &line_number)) { | |
1413 | enum ofputil_protocol usable; | |
1414 | char *error; | |
1415 | ||
1416 | if (*n_gms >= allocated_gms) { | |
2134b5ec SH |
1417 | size_t i; |
1418 | ||
7395c052 | 1419 | *gms = x2nrealloc(*gms, &allocated_gms, sizeof **gms); |
2134b5ec SH |
1420 | for (i = 0; i < *n_gms; i++) { |
1421 | list_moved(&(*gms)[i].buckets); | |
1422 | } | |
7395c052 NZ |
1423 | } |
1424 | error = parse_ofp_group_mod_str(&(*gms)[*n_gms], command, ds_cstr(&s), | |
1425 | &usable); | |
1426 | if (error) { | |
1427 | size_t i; | |
1428 | ||
1429 | for (i = 0; i < *n_gms; i++) { | |
1430 | ofputil_bucket_list_destroy(&(*gms)[i].buckets); | |
1431 | } | |
1432 | free(*gms); | |
1433 | *gms = NULL; | |
1434 | *n_gms = 0; | |
1435 | ||
1436 | ds_destroy(&s); | |
1437 | if (stream != stdin) { | |
1438 | fclose(stream); | |
1439 | } | |
1440 | ||
1441 | return xasprintf("%s:%d: %s", file_name, line_number, error); | |
1442 | } | |
1443 | *usable_protocols &= usable; | |
1444 | *n_gms += 1; | |
1445 | } | |
1446 | ||
1447 | ds_destroy(&s); | |
1448 | if (stream != stdin) { | |
1449 | fclose(stream); | |
1450 | } | |
1451 | return NULL; | |
1452 | } |