]>
Commit | Line | Data |
---|---|---|
064af421 BP |
1 | /* |
2 | * Distributed under the terms of the GNU GPL version 2. | |
00ba5f3b | 3 | * Copyright (c) 2007, 2008, 2009, 2010 Nicira Networks. |
a14bc59f BP |
4 | * |
5 | * Significant portions of this file may be copied from parts of the Linux | |
6 | * kernel, by Linus Torvalds and others. | |
064af421 BP |
7 | */ |
8 | ||
9 | /* Functions for executing flow actions. */ | |
10 | ||
11 | #include <linux/skbuff.h> | |
12 | #include <linux/in.h> | |
13 | #include <linux/ip.h> | |
14 | #include <linux/tcp.h> | |
15 | #include <linux/udp.h> | |
16 | #include <linux/in6.h> | |
17 | #include <linux/if_vlan.h> | |
f1193301 | 18 | #include <net/inet_ecn.h> |
064af421 BP |
19 | #include <net/ip.h> |
20 | #include <net/checksum.h> | |
f2459fe7 | 21 | |
064af421 | 22 | #include "actions.h" |
f2459fe7 | 23 | #include "datapath.h" |
064af421 | 24 | #include "openvswitch/datapath-protocol.h" |
f2459fe7 | 25 | #include "vport.h" |
064af421 | 26 | |
0cd8a05e JG |
27 | static struct sk_buff * |
28 | make_writable(struct sk_buff *skb, unsigned min_headroom, gfp_t gfp) | |
064af421 BP |
29 | { |
30 | if (skb_shared(skb) || skb_cloned(skb)) { | |
0cd8a05e JG |
31 | struct sk_buff *nskb; |
32 | unsigned headroom = max(min_headroom, skb_headroom(skb)); | |
33 | ||
34 | nskb = skb_copy_expand(skb, headroom, skb_tailroom(skb), gfp); | |
064af421 | 35 | if (nskb) { |
ff6402a9 | 36 | set_skb_csum_bits(skb, nskb); |
064af421 BP |
37 | kfree_skb(skb); |
38 | return nskb; | |
39 | } | |
40 | } else { | |
41 | unsigned int hdr_len = (skb_transport_offset(skb) | |
42 | + sizeof(struct tcphdr)); | |
43 | if (pskb_may_pull(skb, min(hdr_len, skb->len))) | |
44 | return skb; | |
45 | } | |
46 | kfree_skb(skb); | |
47 | return NULL; | |
48 | } | |
49 | ||
659586ef JG |
50 | static void set_tunnel(struct sk_buff *skb, struct odp_flow_key *key, |
51 | __be32 tun_id) | |
52 | { | |
53 | OVS_CB(skb)->tun_id = key->tun_id = tun_id; | |
54 | } | |
064af421 BP |
55 | |
56 | static struct sk_buff * | |
57 | vlan_pull_tag(struct sk_buff *skb) | |
58 | { | |
59 | struct vlan_ethhdr *vh = vlan_eth_hdr(skb); | |
60 | struct ethhdr *eh; | |
61 | ||
064af421 BP |
62 | /* Verify we were given a vlan packet */ |
63 | if (vh->h_vlan_proto != htons(ETH_P_8021Q)) | |
64 | return skb; | |
65 | ||
635c9298 JG |
66 | if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE) |
67 | skb->csum = csum_sub(skb->csum, csum_partial(skb->data | |
68 | + ETH_HLEN, VLAN_HLEN, 0)); | |
69 | ||
064af421 BP |
70 | memmove(skb->data + VLAN_HLEN, skb->data, 2 * VLAN_ETH_ALEN); |
71 | ||
72 | eh = (struct ethhdr *)skb_pull(skb, VLAN_HLEN); | |
73 | ||
74 | skb->protocol = eh->h_proto; | |
75 | skb->mac_header += VLAN_HLEN; | |
76 | ||
77 | return skb; | |
78 | } | |
79 | ||
80 | ||
81 | static struct sk_buff * | |
82 | modify_vlan_tci(struct datapath *dp, struct sk_buff *skb, | |
83 | struct odp_flow_key *key, const union odp_action *a, | |
84 | int n_actions, gfp_t gfp) | |
85 | { | |
86 | u16 tci, mask; | |
87 | ||
88 | if (a->type == ODPAT_SET_VLAN_VID) { | |
89 | tci = ntohs(a->vlan_vid.vlan_vid); | |
90 | mask = VLAN_VID_MASK; | |
11cdf5e6 | 91 | key->dl_vlan = a->vlan_vid.vlan_vid; |
064af421 | 92 | } else { |
d42c4f8d | 93 | tci = a->vlan_pcp.vlan_pcp << VLAN_PCP_SHIFT; |
064af421 | 94 | mask = VLAN_PCP_MASK; |
11cdf5e6 | 95 | key->dl_vlan_pcp = a->vlan_pcp.vlan_pcp; |
064af421 BP |
96 | } |
97 | ||
0cd8a05e | 98 | skb = make_writable(skb, VLAN_HLEN, gfp); |
064af421 BP |
99 | if (!skb) |
100 | return ERR_PTR(-ENOMEM); | |
101 | ||
102 | if (skb->protocol == htons(ETH_P_8021Q)) { | |
103 | /* Modify vlan id, but maintain other TCI values */ | |
104 | struct vlan_ethhdr *vh = vlan_eth_hdr(skb); | |
635c9298 JG |
105 | __be16 old_tci = vh->h_vlan_TCI; |
106 | ||
064af421 | 107 | vh->h_vlan_TCI = htons((ntohs(vh->h_vlan_TCI) & ~mask) | tci); |
635c9298 JG |
108 | |
109 | if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE) { | |
110 | __be16 diff[] = { ~old_tci, vh->h_vlan_TCI }; | |
111 | ||
112 | skb->csum = ~csum_partial((char *)diff, sizeof(diff), | |
113 | ~skb->csum); | |
114 | } | |
064af421 BP |
115 | } else { |
116 | /* Add vlan header */ | |
117 | ||
118 | /* Set up checksumming pointers for checksum-deferred packets | |
119 | * on Xen. Otherwise, dev_queue_xmit() will try to do this | |
120 | * when we send the packet out on the wire, and it will fail at | |
121 | * that point because skb_checksum_setup() will not look inside | |
122 | * an 802.1Q header. */ | |
b2f460c7 | 123 | vswitch_skb_checksum_setup(skb); |
064af421 BP |
124 | |
125 | /* GSO is not implemented for packets with an 802.1Q header, so | |
126 | * we have to do segmentation before we add that header. | |
127 | * | |
128 | * GSO does work with hardware-accelerated VLAN tagging, but we | |
129 | * can't use hardware-accelerated VLAN tagging since it | |
130 | * requires the device to have a VLAN group configured (with | |
131 | * e.g. vconfig(8)) and we don't do that. | |
132 | * | |
133 | * Having to do this here may be a performance loss, since we | |
134 | * can't take advantage of TSO hardware support, although it | |
135 | * does not make a measurable network performance difference | |
136 | * for 1G Ethernet. Fixing that would require patching the | |
137 | * kernel (either to add GSO support to the VLAN protocol or to | |
138 | * support hardware-accelerated VLAN tagging without VLAN | |
139 | * groups configured). */ | |
140 | if (skb_is_gso(skb)) { | |
141 | struct sk_buff *segs; | |
142 | ||
143 | segs = skb_gso_segment(skb, 0); | |
144 | kfree_skb(skb); | |
145 | if (unlikely(IS_ERR(segs))) | |
146 | return ERR_CAST(segs); | |
147 | ||
148 | do { | |
149 | struct sk_buff *nskb = segs->next; | |
150 | int err; | |
151 | ||
152 | segs->next = NULL; | |
153 | ||
635c9298 JG |
154 | /* GSO can change the checksum type so update.*/ |
155 | compute_ip_summed(segs, true); | |
156 | ||
064af421 BP |
157 | segs = __vlan_put_tag(segs, tci); |
158 | err = -ENOMEM; | |
159 | if (segs) { | |
160 | struct odp_flow_key segkey = *key; | |
161 | err = execute_actions(dp, segs, | |
162 | &segkey, a + 1, | |
163 | n_actions - 1, | |
164 | gfp); | |
165 | } | |
166 | ||
167 | if (unlikely(err)) { | |
168 | while ((segs = nskb)) { | |
169 | nskb = segs->next; | |
170 | segs->next = NULL; | |
171 | kfree_skb(segs); | |
172 | } | |
173 | return ERR_PTR(err); | |
174 | } | |
175 | ||
176 | segs = nskb; | |
177 | } while (segs->next); | |
178 | ||
179 | skb = segs; | |
635c9298 | 180 | compute_ip_summed(skb, true); |
064af421 BP |
181 | } |
182 | ||
183 | /* The hardware-accelerated version of vlan_put_tag() works | |
184 | * only for a device that has a VLAN group configured (with | |
185 | * e.g. vconfig(8)), so call the software-only version | |
186 | * __vlan_put_tag() directly instead. | |
187 | */ | |
188 | skb = __vlan_put_tag(skb, tci); | |
189 | if (!skb) | |
190 | return ERR_PTR(-ENOMEM); | |
635c9298 JG |
191 | |
192 | /* GSO doesn't fix up the hardware computed checksum so this | |
193 | * will only be hit in the non-GSO case. */ | |
194 | if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE) | |
195 | skb->csum = csum_add(skb->csum, csum_partial(skb->data | |
196 | + ETH_HLEN, VLAN_HLEN, 0)); | |
064af421 BP |
197 | } |
198 | ||
199 | return skb; | |
200 | } | |
201 | ||
202 | static struct sk_buff *strip_vlan(struct sk_buff *skb, | |
203 | struct odp_flow_key *key, gfp_t gfp) | |
204 | { | |
0cd8a05e | 205 | skb = make_writable(skb, 0, gfp); |
064af421 BP |
206 | if (skb) { |
207 | vlan_pull_tag(skb); | |
208 | key->dl_vlan = htons(ODP_VLAN_NONE); | |
209 | } | |
210 | return skb; | |
211 | } | |
212 | ||
213 | static struct sk_buff *set_dl_addr(struct sk_buff *skb, | |
11cdf5e6 | 214 | struct odp_flow_key *key, |
064af421 BP |
215 | const struct odp_action_dl_addr *a, |
216 | gfp_t gfp) | |
217 | { | |
0cd8a05e | 218 | skb = make_writable(skb, 0, gfp); |
064af421 BP |
219 | if (skb) { |
220 | struct ethhdr *eh = eth_hdr(skb); | |
11cdf5e6 JG |
221 | if (a->type == ODPAT_SET_DL_SRC) { |
222 | memcpy(eh->h_source, a->dl_addr, ETH_ALEN); | |
223 | memcpy(key->dl_src, a->dl_addr, ETH_ALEN); | |
224 | } else { | |
225 | memcpy(eh->h_dest, a->dl_addr, ETH_ALEN); | |
226 | memcpy(key->dl_dst, a->dl_addr, ETH_ALEN); | |
227 | } | |
064af421 BP |
228 | } |
229 | return skb; | |
230 | } | |
231 | ||
232 | /* Updates 'sum', which is a field in 'skb''s data, given that a 4-byte field | |
233 | * covered by the sum has been changed from 'from' to 'to'. If set, | |
234 | * 'pseudohdr' indicates that the field is in the TCP or UDP pseudo-header. | |
235 | * Based on nf_proto_csum_replace4. */ | |
236 | static void update_csum(__sum16 *sum, struct sk_buff *skb, | |
237 | __be32 from, __be32 to, int pseudohdr) | |
238 | { | |
239 | __be32 diff[] = { ~from, to }; | |
1cdb82b9 | 240 | |
635c9298 | 241 | if (OVS_CB(skb)->ip_summed != OVS_CSUM_PARTIAL) { |
064af421 BP |
242 | *sum = csum_fold(csum_partial((char *)diff, sizeof(diff), |
243 | ~csum_unfold(*sum))); | |
635c9298 | 244 | if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE && pseudohdr) |
064af421 BP |
245 | skb->csum = ~csum_partial((char *)diff, sizeof(diff), |
246 | ~skb->csum); | |
247 | } else if (pseudohdr) | |
248 | *sum = ~csum_fold(csum_partial((char *)diff, sizeof(diff), | |
249 | csum_unfold(*sum))); | |
250 | } | |
251 | ||
252 | static struct sk_buff *set_nw_addr(struct sk_buff *skb, | |
253 | struct odp_flow_key *key, | |
254 | const struct odp_action_nw_addr *a, | |
255 | gfp_t gfp) | |
256 | { | |
257 | if (key->dl_type != htons(ETH_P_IP)) | |
258 | return skb; | |
259 | ||
0cd8a05e | 260 | skb = make_writable(skb, 0, gfp); |
064af421 BP |
261 | if (skb) { |
262 | struct iphdr *nh = ip_hdr(skb); | |
263 | u32 *f = a->type == ODPAT_SET_NW_SRC ? &nh->saddr : &nh->daddr; | |
264 | u32 old = *f; | |
265 | u32 new = a->nw_addr; | |
266 | ||
267 | if (key->nw_proto == IPPROTO_TCP) { | |
268 | struct tcphdr *th = tcp_hdr(skb); | |
269 | update_csum(&th->check, skb, old, new, 1); | |
270 | } else if (key->nw_proto == IPPROTO_UDP) { | |
271 | struct udphdr *th = udp_hdr(skb); | |
272 | update_csum(&th->check, skb, old, new, 1); | |
273 | } | |
274 | update_csum(&nh->check, skb, old, new, 0); | |
275 | *f = new; | |
11cdf5e6 JG |
276 | |
277 | if (a->type == ODPAT_SET_NW_SRC) | |
278 | key->nw_src = a->nw_addr; | |
279 | else | |
280 | key->nw_dst = a->nw_addr; | |
064af421 BP |
281 | } |
282 | return skb; | |
283 | } | |
284 | ||
959a2ecd JP |
285 | static struct sk_buff *set_nw_tos(struct sk_buff *skb, |
286 | struct odp_flow_key *key, | |
287 | const struct odp_action_nw_tos *a, | |
288 | gfp_t gfp) | |
289 | { | |
290 | if (key->dl_type != htons(ETH_P_IP)) | |
291 | return skb; | |
292 | ||
293 | skb = make_writable(skb, 0, gfp); | |
294 | if (skb) { | |
295 | struct iphdr *nh = ip_hdr(skb); | |
296 | u8 *f = &nh->tos; | |
297 | u8 old = *f; | |
f1193301 | 298 | u8 new; |
959a2ecd | 299 | |
f1193301 | 300 | /* Set the DSCP bits and preserve the ECN bits. */ |
3c5f6de3 | 301 | new = a->nw_tos | (nh->tos & INET_ECN_MASK); |
959a2ecd JP |
302 | update_csum(&nh->check, skb, htons((uint16_t)old), |
303 | htons((uint16_t)new), 0); | |
304 | *f = new; | |
11cdf5e6 | 305 | key->nw_tos = a->nw_tos; |
959a2ecd JP |
306 | } |
307 | return skb; | |
308 | } | |
309 | ||
064af421 BP |
310 | static struct sk_buff * |
311 | set_tp_port(struct sk_buff *skb, struct odp_flow_key *key, | |
312 | const struct odp_action_tp_port *a, | |
313 | gfp_t gfp) | |
314 | { | |
315 | int check_ofs; | |
316 | ||
317 | if (key->dl_type != htons(ETH_P_IP)) | |
318 | return skb; | |
319 | ||
320 | if (key->nw_proto == IPPROTO_TCP) | |
321 | check_ofs = offsetof(struct tcphdr, check); | |
322 | else if (key->nw_proto == IPPROTO_UDP) | |
323 | check_ofs = offsetof(struct udphdr, check); | |
324 | else | |
325 | return skb; | |
326 | ||
0cd8a05e | 327 | skb = make_writable(skb, 0, gfp); |
064af421 BP |
328 | if (skb) { |
329 | struct udphdr *th = udp_hdr(skb); | |
330 | u16 *f = a->type == ODPAT_SET_TP_SRC ? &th->source : &th->dest; | |
331 | u16 old = *f; | |
332 | u16 new = a->tp_port; | |
985224ac | 333 | update_csum((u16*)(skb_transport_header(skb) + check_ofs), |
00ba5f3b | 334 | skb, old, new, 0); |
064af421 | 335 | *f = new; |
11cdf5e6 JG |
336 | if (a->type == ODPAT_SET_TP_SRC) |
337 | key->tp_src = a->tp_port; | |
338 | else | |
339 | key->tp_dst = a->tp_port; | |
064af421 BP |
340 | } |
341 | return skb; | |
342 | } | |
343 | ||
344 | static inline unsigned packet_length(const struct sk_buff *skb) | |
345 | { | |
346 | unsigned length = skb->len - ETH_HLEN; | |
347 | if (skb->protocol == htons(ETH_P_8021Q)) | |
348 | length -= VLAN_HLEN; | |
349 | return length; | |
350 | } | |
351 | ||
064af421 BP |
352 | static void |
353 | do_output(struct datapath *dp, struct sk_buff *skb, int out_port) | |
354 | { | |
f2459fe7 JG |
355 | struct dp_port *p; |
356 | int mtu; | |
064af421 BP |
357 | |
358 | if (!skb) | |
359 | goto error; | |
360 | ||
f2459fe7 | 361 | p = rcu_dereference(dp->ports[out_port]); |
064af421 BP |
362 | if (!p) |
363 | goto error; | |
364 | ||
f2459fe7 JG |
365 | mtu = vport_get_mtu(p->vport); |
366 | if (packet_length(skb) > mtu && !skb_is_gso(skb)) { | |
367 | printk(KERN_WARNING "%s: dropped over-mtu packet: %d > %d\n", | |
368 | dp_name(dp), packet_length(skb), mtu); | |
369 | goto error; | |
370 | } | |
371 | ||
372 | vport_send(p->vport, skb); | |
064af421 BP |
373 | return; |
374 | ||
375 | error: | |
376 | kfree_skb(skb); | |
377 | } | |
378 | ||
379 | /* Never consumes 'skb'. Returns a port that 'skb' should be sent to, -1 if | |
380 | * none. */ | |
381 | static int output_group(struct datapath *dp, __u16 group, | |
382 | struct sk_buff *skb, gfp_t gfp) | |
383 | { | |
384 | struct dp_port_group *g = rcu_dereference(dp->groups[group]); | |
385 | int prev_port = -1; | |
386 | int i; | |
387 | ||
388 | if (!g) | |
389 | return -1; | |
390 | for (i = 0; i < g->n_ports; i++) { | |
f2459fe7 JG |
391 | struct dp_port *p = rcu_dereference(dp->ports[g->ports[i]]); |
392 | if (!p || OVS_CB(skb)->dp_port == p) | |
064af421 BP |
393 | continue; |
394 | if (prev_port != -1) { | |
395 | struct sk_buff *clone = skb_clone(skb, gfp); | |
396 | if (!clone) | |
397 | return -1; | |
398 | do_output(dp, clone, prev_port); | |
399 | } | |
400 | prev_port = p->port_no; | |
401 | } | |
402 | return prev_port; | |
403 | } | |
404 | ||
405 | static int | |
406 | output_control(struct datapath *dp, struct sk_buff *skb, u32 arg, gfp_t gfp) | |
407 | { | |
408 | skb = skb_clone(skb, gfp); | |
409 | if (!skb) | |
410 | return -ENOMEM; | |
411 | return dp_output_control(dp, skb, _ODPL_ACTION_NR, arg); | |
412 | } | |
413 | ||
72b06300 BP |
414 | /* Send a copy of this packet up to the sFlow agent, along with extra |
415 | * information about what happened to it. */ | |
416 | static void sflow_sample(struct datapath *dp, struct sk_buff *skb, | |
56fd8edf | 417 | const union odp_action *a, int n_actions, |
f2459fe7 | 418 | gfp_t gfp, struct dp_port *dp_port) |
72b06300 BP |
419 | { |
420 | struct odp_sflow_sample_header *hdr; | |
421 | unsigned int actlen = n_actions * sizeof(union odp_action); | |
422 | unsigned int hdrlen = sizeof(struct odp_sflow_sample_header); | |
423 | struct sk_buff *nskb; | |
72b06300 BP |
424 | |
425 | nskb = skb_copy_expand(skb, actlen + hdrlen, 0, gfp); | |
426 | if (!nskb) | |
427 | return; | |
428 | ||
429 | memcpy(__skb_push(nskb, actlen), a, actlen); | |
430 | hdr = (struct odp_sflow_sample_header*)__skb_push(nskb, hdrlen); | |
431 | hdr->n_actions = n_actions; | |
f2459fe7 | 432 | hdr->sample_pool = atomic_read(&dp_port->sflow_pool); |
72b06300 BP |
433 | dp_output_control(dp, nskb, _ODPL_SFLOW_NR, 0); |
434 | } | |
435 | ||
064af421 BP |
436 | /* Execute a list of actions against 'skb'. */ |
437 | int execute_actions(struct datapath *dp, struct sk_buff *skb, | |
438 | struct odp_flow_key *key, | |
439 | const union odp_action *a, int n_actions, | |
440 | gfp_t gfp) | |
441 | { | |
442 | /* Every output action needs a separate clone of 'skb', but the common | |
443 | * case is just a single output action, so that doing a clone and | |
444 | * then freeing the original skbuff is wasteful. So the following code | |
445 | * is slightly obscure just to avoid that. */ | |
446 | int prev_port = -1; | |
c1c9c9c4 | 447 | u32 priority = skb->priority; |
a5225dd6 | 448 | int err; |
72b06300 BP |
449 | |
450 | if (dp->sflow_probability) { | |
f2459fe7 | 451 | struct dp_port *p = OVS_CB(skb)->dp_port; |
56fd8edf BP |
452 | if (p) { |
453 | atomic_inc(&p->sflow_pool); | |
454 | if (dp->sflow_probability == UINT_MAX || | |
455 | net_random() < dp->sflow_probability) | |
456 | sflow_sample(dp, skb, a, n_actions, gfp, p); | |
457 | } | |
72b06300 BP |
458 | } |
459 | ||
659586ef JG |
460 | OVS_CB(skb)->tun_id = 0; |
461 | ||
064af421 BP |
462 | for (; n_actions > 0; a++, n_actions--) { |
463 | WARN_ON_ONCE(skb_shared(skb)); | |
464 | if (prev_port != -1) { | |
465 | do_output(dp, skb_clone(skb, gfp), prev_port); | |
466 | prev_port = -1; | |
467 | } | |
468 | ||
469 | switch (a->type) { | |
470 | case ODPAT_OUTPUT: | |
471 | prev_port = a->output.port; | |
472 | break; | |
473 | ||
474 | case ODPAT_OUTPUT_GROUP: | |
475 | prev_port = output_group(dp, a->output_group.group, | |
476 | skb, gfp); | |
477 | break; | |
478 | ||
479 | case ODPAT_CONTROLLER: | |
480 | err = output_control(dp, skb, a->controller.arg, gfp); | |
481 | if (err) { | |
482 | kfree_skb(skb); | |
483 | return err; | |
484 | } | |
485 | break; | |
486 | ||
659586ef JG |
487 | case ODPAT_SET_TUNNEL: |
488 | set_tunnel(skb, key, a->tunnel.tun_id); | |
489 | break; | |
490 | ||
064af421 BP |
491 | case ODPAT_SET_VLAN_VID: |
492 | case ODPAT_SET_VLAN_PCP: | |
493 | skb = modify_vlan_tci(dp, skb, key, a, n_actions, gfp); | |
494 | if (IS_ERR(skb)) | |
495 | return PTR_ERR(skb); | |
496 | break; | |
497 | ||
498 | case ODPAT_STRIP_VLAN: | |
499 | skb = strip_vlan(skb, key, gfp); | |
500 | break; | |
501 | ||
502 | case ODPAT_SET_DL_SRC: | |
503 | case ODPAT_SET_DL_DST: | |
11cdf5e6 | 504 | skb = set_dl_addr(skb, key, &a->dl_addr, gfp); |
064af421 BP |
505 | break; |
506 | ||
507 | case ODPAT_SET_NW_SRC: | |
508 | case ODPAT_SET_NW_DST: | |
509 | skb = set_nw_addr(skb, key, &a->nw_addr, gfp); | |
510 | break; | |
511 | ||
959a2ecd JP |
512 | case ODPAT_SET_NW_TOS: |
513 | skb = set_nw_tos(skb, key, &a->nw_tos, gfp); | |
514 | break; | |
515 | ||
064af421 BP |
516 | case ODPAT_SET_TP_SRC: |
517 | case ODPAT_SET_TP_DST: | |
518 | skb = set_tp_port(skb, key, &a->tp_port, gfp); | |
519 | break; | |
c1c9c9c4 BP |
520 | |
521 | case ODPAT_SET_PRIORITY: | |
522 | skb->priority = a->priority.priority; | |
523 | break; | |
524 | ||
525 | case ODPAT_POP_PRIORITY: | |
526 | skb->priority = priority; | |
527 | break; | |
064af421 BP |
528 | } |
529 | if (!skb) | |
530 | return -ENOMEM; | |
531 | } | |
532 | if (prev_port != -1) | |
533 | do_output(dp, skb, prev_port); | |
534 | else | |
535 | kfree_skb(skb); | |
a5225dd6 | 536 | return 0; |
064af421 | 537 | } |