netdev-native-tnl: Introduce ip_build_header()
[cascardo/ovs.git] / lib / netdev-native-tnl.c
1 /*
2  * Copyright (c) 2016 Nicira, Inc.
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 <errno.h>
20 #include <fcntl.h>
21 #include <sys/socket.h>
22 #include <net/if.h>
23 #include <netinet/in.h>
24 #include <netinet/ip6.h>
25 #include <sys/ioctl.h>
26
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <sys/time.h>
30
31 #include "openvswitch/list.h"
32 #include "byte-order.h"
33 #include "csum.h"
34 #include "daemon.h"
35 #include "dirs.h"
36 #include "dpif.h"
37 #include "dp-packet.h"
38 #include "entropy.h"
39 #include "flow.h"
40 #include "hash.h"
41 #include "hmap.h"
42 #include "id-pool.h"
43 #include "netdev-provider.h"
44 #include "netdev-vport.h"
45 #include "netdev-vport-private.h"
46 #include "odp-netlink.h"
47 #include "dp-packet.h"
48 #include "ovs-router.h"
49 #include "packets.h"
50 #include "poll-loop.h"
51 #include "random.h"
52 #include "route-table.h"
53 #include "shash.h"
54 #include "socket-util.h"
55 #include "timeval.h"
56 #include "netdev-native-tnl.h"
57 #include "openvswitch/vlog.h"
58 #include "unaligned.h"
59 #include "unixctl.h"
60 #include "util.h"
61
62 VLOG_DEFINE_THIS_MODULE(native_tnl);
63 static struct vlog_rate_limit err_rl = VLOG_RATE_LIMIT_INIT(60, 5);
64
65 #define VXLAN_HLEN   (sizeof(struct udp_header) +         \
66                       sizeof(struct vxlanhdr))
67
68 #define GENEVE_BASE_HLEN   (sizeof(struct udp_header) +         \
69                             sizeof(struct genevehdr))
70
71 uint16_t tnl_udp_port_min = 32768;
72 uint16_t tnl_udp_port_max = 61000;
73
74 void *
75 netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
76                   unsigned int *hlen)
77 {
78     void *nh;
79     struct ip_header *ip;
80     struct ovs_16aligned_ip6_hdr *ip6;
81     void *l4;
82     int l3_size;
83
84     nh = dp_packet_l3(packet);
85     ip = nh;
86     ip6 = nh;
87     l4 = dp_packet_l4(packet);
88
89     if (!nh || !l4) {
90         return NULL;
91     }
92
93     *hlen = sizeof(struct eth_header);
94
95     l3_size = dp_packet_size(packet) -
96               ((char *)nh - (char *)dp_packet_data(packet));
97
98     if (IP_VER(ip->ip_ihl_ver) == 4) {
99
100         ovs_be32 ip_src, ip_dst;
101
102         if (csum(ip, IP_IHL(ip->ip_ihl_ver) * 4)) {
103             VLOG_WARN_RL(&err_rl, "ip packet has invalid checksum");
104             return NULL;
105         }
106
107         if (ntohs(ip->ip_tot_len) > l3_size) {
108             VLOG_WARN_RL(&err_rl, "ip packet is truncated (IP length %d, actual %d)",
109                          ntohs(ip->ip_tot_len), l3_size);
110             return NULL;
111         }
112         if (IP_IHL(ip->ip_ihl_ver) * 4 > sizeof(struct ip_header)) {
113             VLOG_WARN_RL(&err_rl, "ip options not supported on tunnel packets "
114                          "(%d bytes)", IP_IHL(ip->ip_ihl_ver) * 4);
115             return NULL;
116         }
117
118         ip_src = get_16aligned_be32(&ip->ip_src);
119         ip_dst = get_16aligned_be32(&ip->ip_dst);
120
121         tnl->ip_src = ip_src;
122         tnl->ip_dst = ip_dst;
123         tnl->ip_tos = ip->ip_tos;
124         tnl->ip_ttl = ip->ip_ttl;
125
126         *hlen += IP_HEADER_LEN;
127
128     } else if (IP_VER(ip->ip_ihl_ver) == 6) {
129
130         memcpy(tnl->ipv6_src.s6_addr, ip6->ip6_src.be16, sizeof ip6->ip6_src);
131         memcpy(tnl->ipv6_dst.s6_addr, ip6->ip6_dst.be16, sizeof ip6->ip6_dst);
132         tnl->ip_tos = 0;
133         tnl->ip_ttl = ip6->ip6_hlim;
134
135         *hlen += IPV6_HEADER_LEN;
136
137     } else {
138         VLOG_WARN_RL(&err_rl, "ipv4 packet has invalid version (%d)",
139                      IP_VER(ip->ip_ihl_ver));
140         return NULL;
141     }
142
143     return l4;
144 }
145
146 /* Pushes the 'size' bytes of 'header' into the headroom of 'packet',
147  * reallocating the packet if necessary.  'header' should contain an Ethernet
148  * header, followed by an IPv4 header (without options), and an L4 header.
149  *
150  * This function sets the IP header's ip_tot_len field (which should be zeroed
151  * as part of 'header') and puts its value into '*ip_tot_size' as well.  Also
152  * updates IP header checksum.
153  *
154  * Return pointer to the L4 header added to 'packet'. */
155 void *
156 netdev_tnl_push_ip_header(struct dp_packet *packet,
157                const void *header, int size, int *ip_tot_size)
158 {
159     struct eth_header *eth;
160     struct ip_header *ip;
161     struct ovs_16aligned_ip6_hdr *ip6;
162
163     eth = dp_packet_push_uninit(packet, size);
164     *ip_tot_size = dp_packet_size(packet) - sizeof (struct eth_header);
165
166     memcpy(eth, header, size);
167
168     if (netdev_tnl_is_header_ipv6(header)) {
169         ip6 = netdev_tnl_ipv6_hdr(eth);
170         *ip_tot_size -= IPV6_HEADER_LEN;
171         ip6->ip6_plen = htons(*ip_tot_size);
172         return ip6 + 1;
173     } else {
174         ip = netdev_tnl_ip_hdr(eth);
175         ip->ip_tot_len = htons(*ip_tot_size);
176         ip->ip_csum = recalc_csum16(ip->ip_csum, 0, ip->ip_tot_len);
177         *ip_tot_size -= IP_HEADER_LEN;
178         return ip + 1;
179     }
180 }
181
182 static void *
183 udp_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
184                    unsigned int *hlen)
185 {
186     struct udp_header *udp;
187
188     udp = netdev_tnl_ip_extract_tnl_md(packet, tnl, hlen);
189     if (!udp) {
190         return NULL;
191     }
192
193     if (udp->udp_csum) {
194         uint32_t csum;
195         if (netdev_tnl_is_header_ipv6(dp_packet_data(packet))) {
196             csum = packet_csum_pseudoheader6(dp_packet_l3(packet));
197         } else {
198             csum = packet_csum_pseudoheader(dp_packet_l3(packet));
199         }
200
201         csum = csum_continue(csum, udp, dp_packet_size(packet) -
202                              ((const unsigned char *)udp -
203                               (const unsigned char *)dp_packet_l2(packet)));
204         if (csum_finish(csum)) {
205             return NULL;
206         }
207         tnl->flags |= FLOW_TNL_F_CSUM;
208     }
209
210     tnl->tp_src = udp->udp_src;
211     tnl->tp_dst = udp->udp_dst;
212
213     return udp + 1;
214 }
215
216
217 void
218 netdev_tnl_push_udp_header(struct dp_packet *packet,
219                            const struct ovs_action_push_tnl *data)
220 {
221     struct udp_header *udp;
222     int ip_tot_size;
223
224     udp = netdev_tnl_push_ip_header(packet, data->header, data->header_len, &ip_tot_size);
225
226     /* set udp src port */
227     udp->udp_src = netdev_tnl_get_src_port(packet);
228     udp->udp_len = htons(ip_tot_size);
229
230     if (udp->udp_csum) {
231         uint32_t csum;
232         if (netdev_tnl_is_header_ipv6(dp_packet_data(packet))) {
233             csum = packet_csum_pseudoheader6(netdev_tnl_ipv6_hdr(dp_packet_data(packet)));
234         } else {
235             csum = packet_csum_pseudoheader(netdev_tnl_ip_hdr(dp_packet_data(packet)));
236         }
237
238         csum = csum_continue(csum, udp, ip_tot_size);
239         udp->udp_csum = csum_finish(csum);
240
241         if (!udp->udp_csum) {
242             udp->udp_csum = htons(0xffff);
243         }
244     }
245 }
246
247 static void *
248 eth_build_header(struct ovs_action_push_tnl *data,
249                  const struct netdev_tnl_build_header_params *params)
250 {
251     uint16_t eth_proto = params->is_ipv6 ? ETH_TYPE_IPV6 : ETH_TYPE_IP;
252     struct eth_header *eth;
253
254     memset(data->header, 0, sizeof data->header);
255
256     eth = (struct eth_header *)data->header;
257     eth->eth_dst = params->dmac;
258     eth->eth_src = params->smac;
259     eth->eth_type = htons(eth_proto);
260     data->header_len = sizeof(struct eth_header);
261     return eth + 1;
262 }
263
264 void *
265 netdev_tnl_ip_build_header(struct ovs_action_push_tnl *data,
266                            const struct netdev_tnl_build_header_params *params,
267                            uint8_t next_proto)
268 {
269     void *l3;
270
271     l3 = eth_build_header(data, params);
272     if (!params->is_ipv6) {
273         ovs_be32 ip_src = in6_addr_get_mapped_ipv4(params->s_ip);
274         struct ip_header *ip;
275
276         ip = (struct ip_header *) l3;
277
278         ip->ip_ihl_ver = IP_IHL_VER(5, 4);
279         ip->ip_tos = params->flow->tunnel.ip_tos;
280         ip->ip_ttl = params->flow->tunnel.ip_ttl;
281         ip->ip_proto = next_proto;
282         put_16aligned_be32(&ip->ip_src, ip_src);
283         put_16aligned_be32(&ip->ip_dst, params->flow->tunnel.ip_dst);
284
285         ip->ip_frag_off = (params->flow->tunnel.flags & FLOW_TNL_F_DONT_FRAGMENT) ?
286                           htons(IP_DF) : 0;
287
288         ip->ip_csum = csum(ip, sizeof *ip);
289
290         data->header_len += IP_HEADER_LEN;
291         return ip + 1;
292     } else {
293         struct ovs_16aligned_ip6_hdr *ip6;
294
295         ip6 = (struct ovs_16aligned_ip6_hdr *) l3;
296
297         ip6->ip6_vfc = 0x60;
298         ip6->ip6_hlim = params->flow->tunnel.ip_ttl;
299         ip6->ip6_nxt = next_proto;
300         memcpy(&ip6->ip6_src, params->s_ip, sizeof(ovs_be32[4]));
301         memcpy(&ip6->ip6_dst, &params->flow->tunnel.ipv6_dst, sizeof(ovs_be32[4]));
302
303         data->header_len += IPV6_HEADER_LEN;
304         return ip6 + 1;
305     }
306 }
307
308 static void *
309 udp_build_header(struct netdev_tunnel_config *tnl_cfg,
310                  struct ovs_action_push_tnl *data,
311                  const struct netdev_tnl_build_header_params *params)
312 {
313     struct udp_header *udp;
314
315     udp = netdev_tnl_ip_build_header(data, params, IPPROTO_UDP);
316     udp->udp_dst = tnl_cfg->dst_port;
317
318     if (params->is_ipv6 || params->flow->tunnel.flags & FLOW_TNL_F_CSUM) {
319         /* Write a value in now to mark that we should compute the checksum
320          * later. 0xffff is handy because it is transparent to the
321          * calculation. */
322         udp->udp_csum = htons(0xffff);
323     }
324     data->header_len += sizeof *udp;
325     return udp + 1;
326 }
327
328 static int
329 gre_header_len(ovs_be16 flags)
330 {
331     int hlen = 4;
332
333     if (flags & htons(GRE_CSUM)) {
334         hlen += 4;
335     }
336     if (flags & htons(GRE_KEY)) {
337         hlen += 4;
338     }
339     if (flags & htons(GRE_SEQ)) {
340         hlen += 4;
341     }
342     return hlen;
343 }
344
345 static int
346 parse_gre_header(struct dp_packet *packet,
347                  struct flow_tnl *tnl)
348 {
349     const struct gre_base_hdr *greh;
350     ovs_16aligned_be32 *options;
351     int hlen;
352     unsigned int ulen;
353
354     greh = netdev_tnl_ip_extract_tnl_md(packet, tnl, &ulen);
355     if (!greh) {
356         return -EINVAL;
357     }
358
359     if (greh->flags & ~(htons(GRE_CSUM | GRE_KEY | GRE_SEQ))) {
360         return -EINVAL;
361     }
362
363     if (greh->protocol != htons(ETH_TYPE_TEB)) {
364         return -EINVAL;
365     }
366
367     hlen = ulen + gre_header_len(greh->flags);
368     if (hlen > dp_packet_size(packet)) {
369         return -EINVAL;
370     }
371
372     options = (ovs_16aligned_be32 *)(greh + 1);
373     if (greh->flags & htons(GRE_CSUM)) {
374         ovs_be16 pkt_csum;
375
376         pkt_csum = csum(greh, dp_packet_size(packet) -
377                               ((const unsigned char *)greh -
378                                (const unsigned char *)dp_packet_l2(packet)));
379         if (pkt_csum) {
380             return -EINVAL;
381         }
382         tnl->flags = FLOW_TNL_F_CSUM;
383         options++;
384     }
385
386     if (greh->flags & htons(GRE_KEY)) {
387         tnl->tun_id = (OVS_FORCE ovs_be64) ((OVS_FORCE uint64_t)(get_16aligned_be32(options)) << 32);
388         tnl->flags |= FLOW_TNL_F_KEY;
389         options++;
390     }
391
392     if (greh->flags & htons(GRE_SEQ)) {
393         options++;
394     }
395
396     return hlen;
397 }
398
399 struct dp_packet *
400 netdev_gre_pop_header(struct dp_packet *packet)
401 {
402     struct pkt_metadata *md = &packet->md;
403     struct flow_tnl *tnl = &md->tunnel;
404     int hlen = sizeof(struct eth_header) + 4;
405
406     hlen += netdev_tnl_is_header_ipv6(dp_packet_data(packet)) ?
407             IPV6_HEADER_LEN : IP_HEADER_LEN;
408
409     pkt_metadata_init_tnl(md);
410     if (hlen > dp_packet_size(packet)) {
411         goto err;
412     }
413
414     hlen = parse_gre_header(packet, tnl);
415     if (hlen < 0) {
416         goto err;
417     }
418
419     dp_packet_reset_packet(packet, hlen);
420
421     return packet;
422 err:
423     dp_packet_delete(packet);
424     return NULL;
425 }
426
427 void
428 netdev_gre_push_header(struct dp_packet *packet,
429                        const struct ovs_action_push_tnl *data)
430 {
431     struct gre_base_hdr *greh;
432     int ip_tot_size;
433
434     greh = netdev_tnl_push_ip_header(packet, data->header, data->header_len, &ip_tot_size);
435
436     if (greh->flags & htons(GRE_CSUM)) {
437         ovs_be16 *csum_opt = (ovs_be16 *) (greh + 1);
438         *csum_opt = csum(greh, ip_tot_size);
439     }
440 }
441
442 int
443 netdev_gre_build_header(const struct netdev *netdev,
444                         struct ovs_action_push_tnl *data,
445                         const struct netdev_tnl_build_header_params *params)
446 {
447     struct netdev_vport *dev = netdev_vport_cast(netdev);
448     struct netdev_tunnel_config *tnl_cfg;
449     struct gre_base_hdr *greh;
450     ovs_16aligned_be32 *options;
451     unsigned int hlen;
452
453     /* XXX: RCUfy tnl_cfg. */
454     ovs_mutex_lock(&dev->mutex);
455     tnl_cfg = &dev->tnl_cfg;
456
457     greh = netdev_tnl_ip_build_header(data, params, IPPROTO_GRE);
458
459     greh->protocol = htons(ETH_TYPE_TEB);
460     greh->flags = 0;
461
462     options = (ovs_16aligned_be32 *) (greh + 1);
463     if (params->flow->tunnel.flags & FLOW_TNL_F_CSUM) {
464         greh->flags |= htons(GRE_CSUM);
465         put_16aligned_be32(options, 0);
466         options++;
467     }
468
469     if (tnl_cfg->out_key_present) {
470         greh->flags |= htons(GRE_KEY);
471         put_16aligned_be32(options, (OVS_FORCE ovs_be32)
472                                     ((OVS_FORCE uint64_t) params->flow->tunnel.tun_id >> 32));
473         options++;
474     }
475
476     ovs_mutex_unlock(&dev->mutex);
477
478     hlen = (uint8_t *) options - (uint8_t *) greh;
479
480     data->header_len += hlen;
481     data->tnl_type = OVS_VPORT_TYPE_GRE;
482     return 0;
483 }
484
485 struct dp_packet *
486 netdev_vxlan_pop_header(struct dp_packet *packet)
487 {
488     struct pkt_metadata *md = &packet->md;
489     struct flow_tnl *tnl = &md->tunnel;
490     struct vxlanhdr *vxh;
491     unsigned int hlen;
492
493     pkt_metadata_init_tnl(md);
494     if (VXLAN_HLEN > dp_packet_l4_size(packet)) {
495         goto err;
496     }
497
498     vxh = udp_extract_tnl_md(packet, tnl, &hlen);
499     if (!vxh) {
500         goto err;
501     }
502
503     if (get_16aligned_be32(&vxh->vx_flags) != htonl(VXLAN_FLAGS) ||
504        (get_16aligned_be32(&vxh->vx_vni) & htonl(0xff))) {
505         VLOG_WARN_RL(&err_rl, "invalid vxlan flags=%#x vni=%#x\n",
506                      ntohl(get_16aligned_be32(&vxh->vx_flags)),
507                      ntohl(get_16aligned_be32(&vxh->vx_vni)));
508         goto err;
509     }
510     tnl->tun_id = htonll(ntohl(get_16aligned_be32(&vxh->vx_vni)) >> 8);
511     tnl->flags |= FLOW_TNL_F_KEY;
512
513     dp_packet_reset_packet(packet, hlen + VXLAN_HLEN);
514
515     return packet;
516 err:
517     dp_packet_delete(packet);
518     return NULL;
519 }
520
521 int
522 netdev_vxlan_build_header(const struct netdev *netdev,
523                           struct ovs_action_push_tnl *data,
524                           const struct netdev_tnl_build_header_params *params)
525 {
526     struct netdev_vport *dev = netdev_vport_cast(netdev);
527     struct netdev_tunnel_config *tnl_cfg;
528     struct vxlanhdr *vxh;
529
530     /* XXX: RCUfy tnl_cfg. */
531     ovs_mutex_lock(&dev->mutex);
532     tnl_cfg = &dev->tnl_cfg;
533
534     vxh = udp_build_header(tnl_cfg, data, params);
535
536     put_16aligned_be32(&vxh->vx_flags, htonl(VXLAN_FLAGS));
537     put_16aligned_be32(&vxh->vx_vni, htonl(ntohll(params->flow->tunnel.tun_id) << 8));
538
539     ovs_mutex_unlock(&dev->mutex);
540     data->header_len += sizeof *vxh;
541     data->tnl_type = OVS_VPORT_TYPE_VXLAN;
542     return 0;
543 }
544
545 struct dp_packet *
546 netdev_geneve_pop_header(struct dp_packet *packet)
547 {
548     struct pkt_metadata *md = &packet->md;
549     struct flow_tnl *tnl = &md->tunnel;
550     struct genevehdr *gnh;
551     unsigned int hlen, opts_len, ulen;
552
553     pkt_metadata_init_tnl(md);
554     if (GENEVE_BASE_HLEN > dp_packet_l4_size(packet)) {
555         VLOG_WARN_RL(&err_rl, "geneve packet too small: min header=%u packet size=%"PRIuSIZE"\n",
556                      (unsigned int)GENEVE_BASE_HLEN, dp_packet_l4_size(packet));
557         goto err;
558     }
559
560     gnh = udp_extract_tnl_md(packet, tnl, &ulen);
561     if (!gnh) {
562         goto err;
563     }
564
565     opts_len = gnh->opt_len * 4;
566     hlen = ulen + GENEVE_BASE_HLEN + opts_len;
567     if (hlen > dp_packet_size(packet)) {
568         VLOG_WARN_RL(&err_rl, "geneve packet too small: header len=%u packet size=%u\n",
569                      hlen, dp_packet_size(packet));
570         goto err;
571     }
572
573     if (gnh->ver != 0) {
574         VLOG_WARN_RL(&err_rl, "unknown geneve version: %"PRIu8"\n", gnh->ver);
575         goto err;
576     }
577
578     if (gnh->proto_type != htons(ETH_TYPE_TEB)) {
579         VLOG_WARN_RL(&err_rl, "unknown geneve encapsulated protocol: %#x\n",
580                      ntohs(gnh->proto_type));
581         goto err;
582     }
583
584     tnl->flags |= gnh->oam ? FLOW_TNL_F_OAM : 0;
585     tnl->tun_id = htonll(ntohl(get_16aligned_be32(&gnh->vni)) >> 8);
586     tnl->flags |= FLOW_TNL_F_KEY;
587
588     memcpy(tnl->metadata.opts.gnv, gnh->options, opts_len);
589     tnl->metadata.present.len = opts_len;
590     tnl->flags |= FLOW_TNL_F_UDPIF;
591
592     dp_packet_reset_packet(packet, hlen);
593
594     return packet;
595 err:
596     dp_packet_delete(packet);
597     return NULL;
598 }
599
600 int
601 netdev_geneve_build_header(const struct netdev *netdev,
602                            struct ovs_action_push_tnl *data,
603                            const struct netdev_tnl_build_header_params *params)
604 {
605     struct netdev_vport *dev = netdev_vport_cast(netdev);
606     struct netdev_tunnel_config *tnl_cfg;
607     struct genevehdr *gnh;
608     int opt_len;
609     bool crit_opt;
610
611     /* XXX: RCUfy tnl_cfg. */
612     ovs_mutex_lock(&dev->mutex);
613     tnl_cfg = &dev->tnl_cfg;
614
615     gnh = udp_build_header(tnl_cfg, data, params);
616
617     put_16aligned_be32(&gnh->vni, htonl(ntohll(params->flow->tunnel.tun_id) << 8));
618
619     ovs_mutex_unlock(&dev->mutex);
620
621     opt_len = tun_metadata_to_geneve_header(&params->flow->tunnel,
622                                             gnh->options, &crit_opt);
623
624     gnh->opt_len = opt_len / 4;
625     gnh->oam = !!(params->flow->tunnel.flags & FLOW_TNL_F_OAM);
626     gnh->critical = crit_opt ? 1 : 0;
627     gnh->proto_type = htons(ETH_TYPE_TEB);
628
629     data->header_len += sizeof *gnh + opt_len;
630     data->tnl_type = OVS_VPORT_TYPE_GENEVE;
631     return 0;
632 }
633
634 \f
635 void
636 netdev_tnl_egress_port_range(struct unixctl_conn *conn, int argc,
637                              const char *argv[], void *aux OVS_UNUSED)
638 {
639     int val1, val2;
640
641     if (argc < 3) {
642         struct ds ds = DS_EMPTY_INITIALIZER;
643
644         ds_put_format(&ds, "Tunnel UDP source port range: %"PRIu16"-%"PRIu16"\n",
645                             tnl_udp_port_min, tnl_udp_port_max);
646
647         unixctl_command_reply(conn, ds_cstr(&ds));
648         ds_destroy(&ds);
649         return;
650     }
651
652     if (argc != 3) {
653         return;
654     }
655
656     val1 = atoi(argv[1]);
657     if (val1 <= 0 || val1 > UINT16_MAX) {
658         unixctl_command_reply(conn, "Invalid min.");
659         return;
660     }
661     val2 = atoi(argv[2]);
662     if (val2 <= 0 || val2 > UINT16_MAX) {
663         unixctl_command_reply(conn, "Invalid max.");
664         return;
665     }
666
667     if (val1 > val2) {
668         tnl_udp_port_min = val2;
669         tnl_udp_port_max = val1;
670     } else {
671         tnl_udp_port_min = val1;
672         tnl_udp_port_max = val2;
673     }
674     seq_change(tnl_conf_seq);
675
676     unixctl_command_reply(conn, "OK");
677 }