blob: 980e3c77ee0b5f33863438974b0992b7e321f701 [file] [log] [blame]
Pablo Neira Ayuso130c4fb2011-06-23 21:15:53 +02001#include "internal.h"
2
3#include <stdio.h>
4#include <stdbool.h>
5#include <unistd.h>
6#include <stdlib.h>
7#include <errno.h>
8#include <string.h>
9#include <time.h>
10#include <sys/fcntl.h>
11#include <sys/socket.h>
12#include <sys/ioctl.h>
13#include <arpa/inet.h>
14
15#include <osmocom/core/select.h>
16#include <osmocom/core/bitvec.h>
17#include <osmocom/gsm/tlv.h>
18#include <osmocom/core/msgb.h>
19#include <osmocom/core/logging.h>
20#include <talloc.h>
21#include <osmocom/abis/e1_input.h>
22#include <osmocom/abis/ipaccess.h>
23#include <osmocom/core/socket.h>
24#include <osmocom/abis/logging.h>
25
26#include <osmocom/abis/ipa.h>
27#include <osmocom/vty/vty.h>
28#include <osmocom/vty/command.h>
29
30static void *tall_ipa_proxy_ctx;
31
32/*
33 * data structures used by the IPA VTY commands
34 */
35static LLIST_HEAD(ipa_instance_list);
36
37enum ipa_proxy_instance_net_type {
38 IPA_INSTANCE_T_NONE,
39 IPA_INSTANCE_T_BIND,
40 IPA_INSTANCE_T_CONNECT,
41 IPA_INSTANCE_T_MAX
42};
43
44struct ipa_proxy_instance_net {
45 char *addr;
46 uint16_t port;
47 enum ipa_proxy_instance_net_type type;
48};
49
50struct ipa_proxy_instance {
51 struct llist_head head;
52#define IPA_INSTANCE_NAME 16
53 char name[IPA_INSTANCE_NAME];
54 struct ipa_proxy_instance_net net;
55 int refcnt;
56};
57
58static LLIST_HEAD(ipa_proxy_route_list);
59
60/* Several routes pointing to the same instances share this. */
61struct ipa_proxy_route_shared {
62 int refcnt;
63
64 /* this file descriptor is used to accept() new connections. */
65 struct osmo_fd bfd;
66
67 struct {
68 struct ipa_proxy_instance *inst;
69 struct bitvec streamid_map;
70 uint8_t streamid_map_data[(0xff+1)/8];
71 uint8_t streamid[0xff];
72 } src;
73 struct {
74 struct ipa_proxy_instance *inst;
75 struct bitvec streamid_map;
76 uint8_t streamid_map_data[(0xff+1)/8];
77 uint8_t streamid[0xff];
78 } dst;
79
80 struct llist_head conn_list;
81};
82
83/* One route is composed of two instances. */
84struct ipa_proxy_route {
85 struct llist_head head;
86
87 struct {
88 uint8_t streamid;
89 } src;
90 struct {
91 uint8_t streamid;
92 } dst;
93
94 struct ipa_proxy_route_shared *shared;
95};
96
97enum ipa_conn_state {
98 IPA_CONN_S_NONE,
99 IPA_CONN_S_CONNECTING,
100 IPA_CONN_S_CONNECTED,
101 IPA_CONN_S_MAX
102};
103
104/* One route may forward more than one connection. */
105struct ipa_proxy_conn {
106 struct llist_head head;
107
108 struct ipa_server_peer *src;
109 struct ipa_client_link *dst;
110 struct ipa_proxy_route *route;
111};
112
113/*
114 * socket callbacks used by IPA VTY commands
115 */
116static int ipa_sock_dst_cb(struct ipa_client_link *link, struct msgb *msg)
117{
118 struct ipaccess_head *hh;
119 struct ipa_proxy_conn *conn = link->data;
120
121 LOGP(DINP, LOGL_NOTICE, "received message from client side\n");
122
123 hh = (struct ipaccess_head *)msg->data;
124 /* check if we have a route for this message. */
125 if (bitvec_get_bit_pos(
126 &conn->route->shared->dst.streamid_map,
127 hh->proto) != ONE) {
128 LOGP(DINP, LOGL_NOTICE, "we don't have a "
129 "route for streamid 0x%x\n", hh->proto);
130 msgb_free(msg);
131 return 0;
132 }
133 /* mangle message, if required. */
134 hh->proto = conn->route->shared->src.streamid[hh->proto];
135
136 ipa_server_peer_send(conn->src, msg);
137 return 0;
138}
139
140static int ipa_sock_src_cb(struct ipa_server_peer *peer, struct msgb *msg)
141{
142 struct ipaccess_head *hh;
143 struct ipa_proxy_conn *conn = peer->data;
144
145 LOGP(DINP, LOGL_NOTICE, "received message from server side\n");
146
147 hh = (struct ipaccess_head *)msg->data;
148 /* check if we have a route for this message. */
149 if (bitvec_get_bit_pos(&conn->route->shared->src.streamid_map,
150 hh->proto) != ONE) {
151 LOGP(DINP, LOGL_NOTICE, "we don't have a "
152 "route for streamid 0x%x\n", hh->proto);
153 msgb_free(msg);
154 return 0;
155 }
156 /* mangle message, if required. */
157 hh->proto = conn->route->shared->dst.streamid[hh->proto];
158
159 ipa_client_link_send(conn->dst, msg);
160 return 0;
161}
162
163static int
164ipa_sock_src_accept_cb(struct ipa_server_link *link, int fd)
165{
166 int ret;
167 struct ipa_proxy_route *route = link->data;
168 struct ipa_proxy_conn *conn;
169
170 conn = talloc_zero(tall_ipa_proxy_ctx, struct ipa_proxy_conn);
171 if (conn == NULL) {
172 LOGP(DINP, LOGL_ERROR, "cannot allocate memory for "
173 "origin IPA\n");
174 close(fd);
175 return ret;
176 }
177 conn->route = route;
178
179 conn->src = ipa_server_peer_create(tall_ipa_proxy_ctx, link, fd,
180 ipa_sock_src_cb, conn);
181 if (conn->src == NULL) {
182 LOGP(DINP, LOGL_ERROR, "could not create server peer: %s\n",
183 strerror(errno));
184 return -ENOMEM;
185 }
186
187 LOGP(DINP, LOGL_NOTICE, "now trying to connect to destination\n");
188
189 conn->dst = ipa_client_link_create(NULL, NULL,
190 route->shared->dst.inst->net.addr,
191 route->shared->dst.inst->net.port,
192 ipa_sock_dst_cb, conn);
193 if (conn->dst == NULL) {
194 LOGP(DINP, LOGL_ERROR, "could not create client: %s\n",
195 strerror(errno));
196 return -ENOMEM;
197 }
198 if (ipa_client_link_open(conn->dst) < 0) {
199 LOGP(DINP, LOGL_ERROR, "could not start client: %s\n",
200 strerror(errno));
201 return -ENOMEM;
202 }
203 llist_add(&conn->head, &route->shared->conn_list);
204 return ret;
205}
206
207/*
208 * VTY commands for IPA
209 */
210DEFUN(ipa_proxy, ipa_cmd, "ipa", "Configure the ipaccess proxy")
211{
212 vty->index = NULL;
213 vty->node = IPA_NODE;
214 return CMD_SUCCESS;
215}
216
217static int __ipa_instance_add(struct vty *vty, int argc, const char *argv[])
218{
219 struct ipa_proxy_instance *ipi;
220 enum ipa_proxy_instance_net_type type;
221 struct in_addr addr;
222 uint16_t port;
223
224 if (argc < 4)
225 return CMD_ERR_INCOMPLETE;
226
227 llist_for_each_entry(ipi, &ipa_instance_list, head) {
228 if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) != 0)
229 continue;
230
231 vty_out(vty, "%% instance `%s' already exists%s",
232 ipi->name, VTY_NEWLINE);
233 return CMD_WARNING;
234 }
235 if (strncmp(argv[1], "bind", IPA_INSTANCE_NAME) == 0)
236 type = IPA_INSTANCE_T_BIND;
237 else if (strncmp(argv[1], "connect", IPA_INSTANCE_NAME) == 0)
238 type = IPA_INSTANCE_T_CONNECT;
239 else
240 return CMD_ERR_INCOMPLETE;
241
242 if (inet_aton(argv[2], &addr) < 0) {
243 vty_out(vty, "%% invalid address %s%s", argv[1], VTY_NEWLINE);
244 return CMD_WARNING;
245 }
246 port = atoi(argv[3]);
247
248 ipi = talloc_zero(tall_ipa_proxy_ctx, struct ipa_proxy_instance);
249 if (ipi == NULL) {
250 vty_out(vty, "%% can't allocate memory for new instance%s",
251 VTY_NEWLINE);
252 return CMD_WARNING;
253 }
254 strncpy(ipi->name, argv[0], IPA_INSTANCE_NAME);
255 ipi->net.type = type;
256 ipi->net.addr = talloc_strdup(tall_ipa_proxy_ctx, argv[2]);
257 ipi->net.port = port;
258 llist_add_tail(&ipi->head, &ipa_instance_list);
259
260 return CMD_SUCCESS;
261}
262
263DEFUN(ipa_instance_add, ipa_instance_add_cmd,
264 "ipa instance NAME (bind|connect) IP tcp port PORT",
265 "Bind or connect instance to address and port")
266{
267 return __ipa_instance_add(vty, argc, argv);
268}
269
270DEFUN(ipa_instance_del, ipa_instance_del_cmd,
271 "no ipa instance NAME",
272 "Delete instance to address and port")
273{
274 struct ipa_proxy_instance *ipi;
275
276 if (argc < 1)
277 return CMD_ERR_INCOMPLETE;
278
279 llist_for_each_entry(ipi, &ipa_instance_list, head) {
280 if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) != 0)
281 continue;
282
283 if (ipi->refcnt > 0) {
284 vty_out(vty, "%% instance `%s' is in use%s",
285 ipi->name, VTY_NEWLINE);
286 return CMD_WARNING;
287 }
288 llist_del(&ipi->head);
289 talloc_free(ipi);
290 return CMD_SUCCESS;
291 }
292 vty_out(vty, "%% instance `%s' does not exist%s",
293 ipi->name, VTY_NEWLINE);
294
295 return CMD_WARNING;
296}
297
298DEFUN(ipa_instance_show, ipa_instance_show_cmd,
299 "ipa instance show", "Show existing ipaccess proxy instances")
300{
301 struct ipa_proxy_instance *this;
302
303 llist_for_each_entry(this, &ipa_instance_list, head) {
304 vty_out(vty, "instance %s %s %s tcp port %u%s",
305 this->name, this->net.addr,
306 this->net.type == IPA_INSTANCE_T_BIND ?
307 "bind" : "connect",
308 this->net.port, VTY_NEWLINE);
309 }
310 return CMD_SUCCESS;
311}
312
313static int __ipa_route_add(struct vty *vty, int argc, const char *argv[])
314{
315 struct ipa_proxy_instance *ipi = vty->index;
316 struct ipa_proxy_instance *src = NULL, *dst = NULL;
317 uint32_t src_streamid, dst_streamid;
318 struct ipa_proxy_route *route, *matching_route = NULL;
319 struct ipa_proxy_route_shared *shared = NULL;
320 int ret;
321
322 if (argc < 4)
323 return CMD_ERR_INCOMPLETE;
324
325 llist_for_each_entry(ipi, &ipa_instance_list, head) {
326 if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) == 0) {
327 src = ipi;
328 continue;
329 }
330 if (strncmp(ipi->name, argv[2], IPA_INSTANCE_NAME) == 0) {
331 dst = ipi;
332 continue;
333 }
334 }
335 if (src == NULL) {
336 vty_out(vty, "%% instance `%s' does not exists%s",
337 argv[0], VTY_NEWLINE);
338 return CMD_WARNING;
339 }
340 if (dst == NULL) {
341 vty_out(vty, "%% instance `%s' does not exists%s",
342 argv[2], VTY_NEWLINE);
343 return CMD_WARNING;
344 }
345 if (src->net.type != IPA_INSTANCE_T_BIND) {
346 vty_out(vty, "%% instance `%s' is not of bind type%s",
347 argv[0], VTY_NEWLINE);
348 return CMD_WARNING;
349 }
350 if (dst->net.type != IPA_INSTANCE_T_CONNECT) {
351 vty_out(vty, "%% instance `%s' is not of connect type%s",
352 argv[2], VTY_NEWLINE);
353 return CMD_WARNING;
354 }
355 src_streamid = strtoul(argv[1], NULL, 16);
356 if (src_streamid > 0xff) {
357 vty_out(vty, "%% source streamid must be "
358 ">= 0x00 and <= 0xff%s", VTY_NEWLINE);
359 return CMD_WARNING;
360 }
361 dst_streamid = strtoul(argv[3], NULL, 16);
362 if (dst_streamid > 0xff) {
363 vty_out(vty, "%% destination streamid must be "
364 ">= 0x00 and <= 0xff%s", VTY_NEWLINE);
365 return CMD_WARNING;
366 }
367 llist_for_each_entry(route, &ipa_proxy_route_list, head) {
368 if (route->shared->src.inst == src &&
369 route->shared->dst.inst == dst) {
370 if (route->src.streamid == src_streamid &&
371 route->dst.streamid == dst_streamid) {
372 vty_out(vty, "%% this route already exists%s",
373 VTY_NEWLINE);
374 return CMD_WARNING;
375 }
376 matching_route = route;
377 break;
378 }
379 }
380 /* new route for this configuration. */
381 route = talloc_zero(tall_ipa_proxy_ctx, struct ipa_proxy_route);
382 if (route == NULL) {
383 vty_out(vty, "%% can't allocate memory for new route%s",
384 VTY_NEWLINE);
385 return CMD_WARNING;
386 }
387 route->src.streamid = src_streamid;
388 route->dst.streamid = dst_streamid;
389
390 if (matching_route != NULL) {
391 /* there's already a master route for these configuration. */
392 if (matching_route->shared->src.inst != src) {
393 vty_out(vty, "%% route does not contain "
394 "source instance `%s'%s",
395 argv[0], VTY_NEWLINE);
396 return CMD_WARNING;
397 }
398 if (matching_route->shared->dst.inst != dst) {
399 vty_out(vty, "%% route does not contain "
400 "destination instance `%s'%s",
401 argv[2], VTY_NEWLINE);
402 return CMD_WARNING;
403 }
404 /* use already existing shared routing information. */
405 shared = matching_route->shared;
406 } else {
407 struct ipa_server_link *link;
408
409 /* this is a brand new route, allocate shared routing info. */
410 shared = talloc_zero(tall_ipa_proxy_ctx, struct ipa_proxy_route_shared);
411 if (shared == NULL) {
412 vty_out(vty, "%% can't allocate memory for "
413 "new route shared%s", VTY_NEWLINE);
414 return CMD_WARNING;
415 }
416 shared->src.streamid_map.data_len =
417 sizeof(shared->src.streamid_map_data);
418 shared->src.streamid_map.data =
419 shared->src.streamid_map_data;
420 shared->dst.streamid_map.data_len =
421 sizeof(shared->dst.streamid_map_data);
422 shared->dst.streamid_map.data =
423 shared->dst.streamid_map_data;
424
425 link = ipa_server_link_create(tall_ipa_proxy_ctx, NULL,
426 "0.0.0.0",
427 src->net.port,
428 ipa_sock_src_accept_cb, route);
429 if (link == NULL) {
430 vty_out(vty, "%% can't bind instance `%s' to port%s",
431 src->name, VTY_NEWLINE);
432 return CMD_WARNING;
433 }
434 if (ipa_server_link_open(link) < 0) {
435 vty_out(vty, "%% can't bind instance `%s' to port%s",
436 src->name, VTY_NEWLINE);
437 return CMD_WARNING;
438 }
439 INIT_LLIST_HEAD(&shared->conn_list);
440 }
441 route->shared = shared;
442 src->refcnt++;
443 route->shared->src.inst = src;
444 dst->refcnt++;
445 route->shared->dst.inst = dst;
446 shared->src.streamid[src_streamid] = dst_streamid;
447 shared->dst.streamid[dst_streamid] = src_streamid;
448 ret = bitvec_set_bit_pos(&shared->src.streamid_map, src_streamid, ONE);
449 if (ret < 0) {
450 vty_out(vty, "%% bad bitmask (?)%s", VTY_NEWLINE);
451 return CMD_WARNING;
452 }
453 ret = bitvec_set_bit_pos(&shared->dst.streamid_map, dst_streamid, ONE);
454 if (ret < 0) {
455 vty_out(vty, "%% bad bitmask (?)%s", VTY_NEWLINE);
456 return CMD_WARNING;
457 }
458 shared->refcnt++;
459
460 llist_add_tail(&route->head, &ipa_proxy_route_list);
461 return CMD_SUCCESS;
462}
463
464DEFUN(ipa_route_add, ipa_route_add_cmd,
465 "ipa route instance NAME streamid HEXNUM "
466 "instance NAME streamid HEXNUM", "Add IPA route")
467{
468 return __ipa_route_add(vty, argc, argv);
469}
470
471DEFUN(ipa_route_del, ipa_route_del_cmd,
472 "no ipa route instance NAME streamid HEXNUM "
473 "instance NAME streamid HEXNUM", "Delete IPA route")
474{
475 struct ipa_proxy_instance *ipi = vty->index;
476 struct ipa_proxy_instance *src = NULL, *dst = NULL;
477 uint32_t src_streamid, dst_streamid;
478 struct ipa_proxy_route *route, *matching_route = NULL;
479 struct ipa_proxy_conn *conn, *tmp;
480
481 if (argc < 4)
482 return CMD_ERR_INCOMPLETE;
483
484 llist_for_each_entry(ipi, &ipa_instance_list, head) {
485 if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) == 0) {
486 src = ipi;
487 continue;
488 }
489 if (strncmp(ipi->name, argv[2], IPA_INSTANCE_NAME) == 0) {
490 dst = ipi;
491 continue;
492 }
493 }
494 if (src == NULL) {
495 vty_out(vty, "%% instance `%s' does not exists%s",
496 argv[0], VTY_NEWLINE);
497 return CMD_WARNING;
498 }
499 if (dst == NULL) {
500 vty_out(vty, "%% instance `%s' does not exists%s",
501 argv[2], VTY_NEWLINE);
502 return CMD_WARNING;
503 }
504 if (src->net.type != IPA_INSTANCE_T_BIND) {
505 vty_out(vty, "%% instance `%s' is not of bind type%s",
506 argv[0], VTY_NEWLINE);
507 return CMD_WARNING;
508 }
509 if (dst->net.type != IPA_INSTANCE_T_CONNECT) {
510 vty_out(vty, "%% instance `%s' is not of connect type%s",
511 argv[2], VTY_NEWLINE);
512 return CMD_WARNING;
513 }
514 src_streamid = strtoul(argv[1], NULL, 16);
515 if (src_streamid > 0xff) {
516 vty_out(vty, "%% source streamid must be "
517 ">= 0x00 and <= 0xff%s", VTY_NEWLINE);
518 return CMD_WARNING;
519 }
520 dst_streamid = strtoul(argv[3], NULL, 16);
521 if (dst_streamid > 0xff) {
522 vty_out(vty, "%% destination streamid must be "
523 ">= 0x00 and <= 0xff%s", VTY_NEWLINE);
524 return CMD_WARNING;
525 }
526 llist_for_each_entry(route, &ipa_proxy_route_list, head) {
527 if (route->shared->src.inst == src &&
528 route->shared->dst.inst == dst &&
529 route->src.streamid == src_streamid &&
530 route->dst.streamid == dst_streamid) {
531 matching_route = route;
532 break;
533 }
534 }
535 if (matching_route == NULL) {
536 vty_out(vty, "%% no route with that configuration%s",
537 VTY_NEWLINE);
538 return CMD_WARNING;
539 }
540 /* delete this route from list. */
541 llist_del(&matching_route->head);
542
543 if (--matching_route->shared->refcnt == 0) {
544 /* nobody else using this route, release all resources. */
545 llist_for_each_entry_safe(conn, tmp,
546 &matching_route->shared->conn_list, head) {
547 ipa_server_peer_destroy(conn->src);
548 llist_del(&conn->head);
549 talloc_free(conn);
550 }
551 osmo_fd_unregister(&route->shared->bfd);
552 close(route->shared->bfd.fd);
553 route->shared->bfd.fd = -1;
554
555 talloc_free(route->shared);
556 } else {
557 /* otherwise, revert the mapping that this route applies. */
558 bitvec_set_bit_pos(&matching_route->shared->src.streamid_map,
559 src_streamid, ZERO);
560 bitvec_set_bit_pos(&matching_route->shared->dst.streamid_map,
561 dst_streamid, ZERO);
562 matching_route->shared->src.streamid[src_streamid] = 0x00;
563 matching_route->shared->dst.streamid[dst_streamid] = 0x00;
564 }
565 matching_route->shared->src.inst->refcnt--;
566 matching_route->shared->dst.inst->refcnt--;
567 talloc_free(matching_route);
568
569 return CMD_SUCCESS;
570}
571
572DEFUN(ipa_route_show, ipa_route_show_cmd,
573 "ipa route show", "Show existing ipaccess proxy routes")
574{
575 struct ipa_proxy_route *this;
576
577 llist_for_each_entry(this, &ipa_proxy_route_list, head) {
578 vty_out(vty, "route instance %s streamid 0x%.2x "
579 "instance %s streamid 0x%.2x%s",
580 this->shared->src.inst->name, this->src.streamid,
581 this->shared->dst.inst->name, this->dst.streamid,
582 VTY_NEWLINE);
583 }
584 return CMD_SUCCESS;
585}
586
587/*
588 * Config for ipaccess-proxy
589 */
590DEFUN(ipa_cfg, ipa_cfg_cmd, "ipa", "Configure the ipaccess proxy")
591{
592 vty->index = NULL;
593 vty->node = IPA_NODE;
594 return CMD_SUCCESS;
595}
596
597/* all these below look like enable commands, but without the ipa prefix. */
598DEFUN(ipa_route_cfg_add, ipa_route_cfg_add_cmd,
599 "route instance NAME streamid HEXNUM "
600 "instance NAME streamid HEXNUM", "Add IPA route")
601{
602 return __ipa_route_add(vty, argc, argv);
603}
604
605DEFUN(ipa_instance_cfg_add, ipa_instance_cfg_add_cmd,
606 "instance NAME (bind|connect) IP tcp port PORT",
607 "Bind or connect instance to address and port")
608{
609 return __ipa_instance_add(vty, argc, argv);
610}
611
612struct cmd_node ipa_node = {
613 IPA_NODE,
614 "%s(ipa)#",
615 1,
616};
617
618static int ipa_cfg_write(struct vty *vty)
619{
620 bool heading = false;
621 struct ipa_proxy_instance *inst;
622 struct ipa_proxy_route *route;
623
624 llist_for_each_entry(inst, &ipa_instance_list, head) {
625 if (!heading) {
626 vty_out(vty, "ipa%s", VTY_NEWLINE);
627 heading = true;
628 }
629 vty_out(vty, " instance %s %s %s tcp port %u%s",
630 inst->name,
631 inst->net.type == IPA_INSTANCE_T_BIND ?
632 "bind" : "connect",
633 inst->net.addr,
634 inst->net.port, VTY_NEWLINE);
635 }
636 llist_for_each_entry(route, &ipa_proxy_route_list, head) {
637 vty_out(vty, " route instance %s streamid 0x%.2x "
638 "instance %s streamid 0x%.2x%s",
639 route->shared->src.inst->name, route->src.streamid,
640 route->shared->dst.inst->name, route->dst.streamid,
641 VTY_NEWLINE);
642 }
643 return CMD_SUCCESS;
644}
645
646DEFUN(ournode_exit,
647 ournode_exit_cmd, "exit", "Exit current mode and down to previous mode\n")
648{
649 switch (vty->node) {
650 case IPA_NODE:
651 vty->node = CONFIG_NODE;
652 vty->index = NULL;
653 break;
654 }
655 return CMD_SUCCESS;
656}
657
658DEFUN(ournode_end,
659 ournode_end_cmd, "end", "End current mode and change to enable mode.\n")
660{
661 switch (vty->node) {
662 case IPA_NODE:
663 break;
664 }
665 return CMD_SUCCESS;
666}
667
668void ipa_proxy_vty_init(void)
669{
670 tall_ipa_proxy_ctx =
671 talloc_named_const(libosmo_abis_ctx, 1, "ipa_proxy");
672
673 install_element(ENABLE_NODE, &ipa_cmd);
674 install_element(ENABLE_NODE, &ipa_instance_add_cmd);
675 install_element(ENABLE_NODE, &ipa_instance_del_cmd);
676 install_element(ENABLE_NODE, &ipa_instance_show_cmd);
677 install_element(ENABLE_NODE, &ipa_route_add_cmd);
678 install_element(ENABLE_NODE, &ipa_route_del_cmd);
679 install_element(ENABLE_NODE, &ipa_route_show_cmd);
680
681 install_element(CONFIG_NODE, &ipa_cfg_cmd);
682 install_node(&ipa_node, ipa_cfg_write);
683 install_default(IPA_NODE);
684 install_element(IPA_NODE, &ournode_exit_cmd);
685 install_element(IPA_NODE, &ournode_end_cmd);
686 install_element(IPA_NODE, &ipa_instance_cfg_add_cmd);
687 install_element(IPA_NODE, &ipa_route_cfg_add_cmd);
688}