#include "internal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void *tall_ipa_proxy_ctx; /* * data structures used by the IPA VTY commands */ static LLIST_HEAD(ipa_instance_list); enum ipa_proxy_instance_net_type { IPA_INSTANCE_T_NONE, IPA_INSTANCE_T_BIND, IPA_INSTANCE_T_CONNECT, IPA_INSTANCE_T_MAX }; struct ipa_proxy_instance_net { char *addr; uint16_t port; enum ipa_proxy_instance_net_type type; }; struct ipa_proxy_instance { struct llist_head head; #define IPA_INSTANCE_NAME 16 char name[IPA_INSTANCE_NAME]; struct ipa_proxy_instance_net net; int refcnt; }; static LLIST_HEAD(ipa_proxy_route_list); /* Several routes pointing to the same instances share this. */ struct ipa_proxy_route_shared { int refcnt; /* this file descriptor is used to accept() new connections. */ struct osmo_fd bfd; struct { struct ipa_proxy_instance *inst; struct bitvec streamid_map; uint8_t streamid_map_data[(0xff+1)/8]; uint8_t streamid[0xff + 1]; } src; struct { struct ipa_proxy_instance *inst; struct bitvec streamid_map; uint8_t streamid_map_data[(0xff+1)/8]; uint8_t streamid[0xff + 1]; } dst; struct llist_head conn_list; }; /* One route is composed of two instances. */ struct ipa_proxy_route { struct llist_head head; struct { uint8_t streamid; } src; struct { uint8_t streamid; } dst; struct ipa_proxy_route_shared *shared; }; enum ipa_conn_state { IPA_CONN_S_NONE, IPA_CONN_S_CONNECTING, IPA_CONN_S_CONNECTED, IPA_CONN_S_MAX }; /* One route may forward more than one connection. */ struct ipa_proxy_conn { struct llist_head head; struct ipa_server_conn *src; struct ipa_client_conn *dst; struct ipa_proxy_route *route; }; /* * socket callbacks used by IPA VTY commands */ static int ipa_sock_dst_cb(struct ipa_client_conn *link, struct msgb *msg) { struct ipaccess_head *hh; struct ipa_proxy_conn *conn = link->data; LOGP(DLINP, LOGL_NOTICE, "received message from client side\n"); hh = (struct ipaccess_head *)msg->data; /* check if we have a route for this message. */ if (bitvec_get_bit_pos( &conn->route->shared->dst.streamid_map, hh->proto) != ONE) { LOGP(DLINP, LOGL_NOTICE, "we don't have a " "route for streamid 0x%x\n", hh->proto); msgb_free(msg); return 0; } /* mangle message, if required. */ hh->proto = conn->route->shared->src.streamid[hh->proto]; ipa_server_conn_send(conn->src, msg); return 0; } static int ipa_sock_src_cb(struct ipa_server_conn *peer, struct msgb *msg) { struct ipaccess_head *hh; struct ipa_proxy_conn *conn = peer->data; LOGP(DLINP, LOGL_NOTICE, "received message from server side\n"); hh = (struct ipaccess_head *)msg->data; /* check if we have a route for this message. */ if (bitvec_get_bit_pos(&conn->route->shared->src.streamid_map, hh->proto) != ONE) { LOGP(DLINP, LOGL_NOTICE, "we don't have a " "route for streamid 0x%x\n", hh->proto); msgb_free(msg); return 0; } /* mangle message, if required. */ hh->proto = conn->route->shared->dst.streamid[hh->proto]; ipa_client_conn_send(conn->dst, msg); return 0; } static int ipa_sock_src_accept_cb(struct ipa_server_link *link, int fd) { struct ipa_proxy_route *route = link->data; struct ipa_proxy_conn *conn; conn = talloc_zero(tall_ipa_proxy_ctx, struct ipa_proxy_conn); if (conn == NULL) { LOGP(DLINP, LOGL_ERROR, "cannot allocate memory for " "origin IPA\n"); close(fd); return -ENOMEM; } conn->route = route; conn->src = ipa_server_conn_create(tall_ipa_proxy_ctx, link, fd, ipa_sock_src_cb, NULL, conn); if (conn->src == NULL) { LOGP(DLINP, LOGL_ERROR, "could not create server peer: %s\n", strerror(errno)); return -ENOMEM; } LOGP(DLINP, LOGL_NOTICE, "now trying to connect to destination\n"); conn->dst = ipa_client_conn_create2(NULL, NULL, 0, NULL, 0, route->shared->dst.inst->net.addr, route->shared->dst.inst->net.port, NULL, ipa_sock_dst_cb, NULL, conn); if (conn->dst == NULL) { LOGP(DLINP, LOGL_ERROR, "could not create client: %s\n", strerror(errno)); return -ENOMEM; } if (ipa_client_conn_open(conn->dst) < 0) { LOGP(DLINP, LOGL_ERROR, "could not start client: %s\n", strerror(errno)); return -ENOMEM; } llist_add(&conn->head, &route->shared->conn_list); return 0; } /* * VTY commands for IPA */ static int __ipa_instance_add(struct vty *vty, int argc, const char *argv[]) { struct ipa_proxy_instance *ipi; enum ipa_proxy_instance_net_type type; struct in_addr addr; uint16_t port; if (argc < 4) return CMD_ERR_INCOMPLETE; llist_for_each_entry(ipi, &ipa_instance_list, head) { if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) != 0) continue; vty_out(vty, "%% instance `%s' already exists%s", ipi->name, VTY_NEWLINE); return CMD_WARNING; } if (strncmp(argv[1], "bind", IPA_INSTANCE_NAME) == 0) type = IPA_INSTANCE_T_BIND; else if (strncmp(argv[1], "connect", IPA_INSTANCE_NAME) == 0) type = IPA_INSTANCE_T_CONNECT; else return CMD_ERR_INCOMPLETE; if (inet_aton(argv[2], &addr) < 0) { vty_out(vty, "%% invalid address %s%s", argv[1], VTY_NEWLINE); return CMD_WARNING; } port = atoi(argv[3]); ipi = talloc_zero(tall_ipa_proxy_ctx, struct ipa_proxy_instance); if (ipi == NULL) { vty_out(vty, "%% can't allocate memory for new instance%s", VTY_NEWLINE); return CMD_WARNING; } osmo_strlcpy(ipi->name, argv[0], sizeof(ipi->name)); ipi->net.type = type; ipi->net.addr = talloc_strdup(tall_ipa_proxy_ctx, argv[2]); ipi->net.port = port; llist_add_tail(&ipi->head, &ipa_instance_list); return CMD_SUCCESS; } DEFUN(ipa_instance_add, ipa_instance_add_cmd, "ipa instance NAME (bind|connect) IP tcp port PORT", "Bind or connect instance to address and port") { return __ipa_instance_add(vty, argc, argv); } DEFUN(ipa_instance_del, ipa_instance_del_cmd, "no ipa instance NAME", "Delete instance to address and port") { struct ipa_proxy_instance *ipi; if (argc < 1) return CMD_ERR_INCOMPLETE; llist_for_each_entry(ipi, &ipa_instance_list, head) { if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) != 0) continue; if (ipi->refcnt > 0) { vty_out(vty, "%% instance `%s' is in use%s", ipi->name, VTY_NEWLINE); return CMD_WARNING; } llist_del(&ipi->head); talloc_free(ipi); return CMD_SUCCESS; } vty_out(vty, "%% instance `%s' does not exist%s", ipi->name, VTY_NEWLINE); return CMD_WARNING; } DEFUN(ipa_instance_show, ipa_instance_show_cmd, "ipa instance show", "Show existing ipaccess proxy instances") { struct ipa_proxy_instance *this; llist_for_each_entry(this, &ipa_instance_list, head) { vty_out(vty, "instance %s %s %s tcp port %u%s", this->name, this->net.addr, this->net.type == IPA_INSTANCE_T_BIND ? "bind" : "connect", this->net.port, VTY_NEWLINE); } return CMD_SUCCESS; } static int __ipa_route_add(struct vty *vty, int argc, const char *argv[]) { struct ipa_proxy_instance *ipi = vty->index; struct ipa_proxy_instance *src = NULL, *dst = NULL; uint32_t src_streamid, dst_streamid; struct ipa_proxy_route *route, *matching_route = NULL; struct ipa_proxy_route_shared *shared = NULL; int ret; if (argc < 4) return CMD_ERR_INCOMPLETE; llist_for_each_entry(ipi, &ipa_instance_list, head) { if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) == 0) { src = ipi; continue; } if (strncmp(ipi->name, argv[2], IPA_INSTANCE_NAME) == 0) { dst = ipi; continue; } } if (src == NULL) { vty_out(vty, "%% instance `%s' does not exists%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } if (dst == NULL) { vty_out(vty, "%% instance `%s' does not exists%s", argv[2], VTY_NEWLINE); return CMD_WARNING; } if (src->net.type != IPA_INSTANCE_T_BIND) { vty_out(vty, "%% instance `%s' is not of bind type%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } if (dst->net.type != IPA_INSTANCE_T_CONNECT) { vty_out(vty, "%% instance `%s' is not of connect type%s", argv[2], VTY_NEWLINE); return CMD_WARNING; } src_streamid = strtoul(argv[1], NULL, 16); if (src_streamid > 0xff) { vty_out(vty, "%% source streamid must be " ">= 0x00 and <= 0xff%s", VTY_NEWLINE); return CMD_WARNING; } dst_streamid = strtoul(argv[3], NULL, 16); if (dst_streamid > 0xff) { vty_out(vty, "%% destination streamid must be " ">= 0x00 and <= 0xff%s", VTY_NEWLINE); return CMD_WARNING; } llist_for_each_entry(route, &ipa_proxy_route_list, head) { if (route->shared->src.inst == src && route->shared->dst.inst == dst) { if (route->src.streamid == src_streamid && route->dst.streamid == dst_streamid) { vty_out(vty, "%% this route already exists%s", VTY_NEWLINE); return CMD_WARNING; } matching_route = route; break; } } /* new route for this configuration. */ route = talloc_zero(tall_ipa_proxy_ctx, struct ipa_proxy_route); if (route == NULL) { vty_out(vty, "%% can't allocate memory for new route%s", VTY_NEWLINE); return CMD_WARNING; } route->src.streamid = src_streamid; route->dst.streamid = dst_streamid; if (matching_route != NULL) { /* there's already a master route for these configuration. */ if (matching_route->shared->src.inst != src) { vty_out(vty, "%% route does not contain " "source instance `%s'%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } if (matching_route->shared->dst.inst != dst) { vty_out(vty, "%% route does not contain " "destination instance `%s'%s", argv[2], VTY_NEWLINE); return CMD_WARNING; } /* use already existing shared routing information. */ shared = matching_route->shared; } else { struct ipa_server_link *link; /* this is a brand new route, allocate shared routing info. */ shared = talloc_zero(tall_ipa_proxy_ctx, struct ipa_proxy_route_shared); if (shared == NULL) { vty_out(vty, "%% can't allocate memory for " "new route shared%s", VTY_NEWLINE); return CMD_WARNING; } shared->src.streamid_map.data_len = sizeof(shared->src.streamid_map_data); shared->src.streamid_map.data = shared->src.streamid_map_data; shared->dst.streamid_map.data_len = sizeof(shared->dst.streamid_map_data); shared->dst.streamid_map.data = shared->dst.streamid_map_data; link = ipa_server_link_create(tall_ipa_proxy_ctx, NULL, "0.0.0.0", src->net.port, ipa_sock_src_accept_cb, route); if (link == NULL) { vty_out(vty, "%% can't bind instance `%s' to port%s", src->name, VTY_NEWLINE); return CMD_WARNING; } if (ipa_server_link_open(link) < 0) { vty_out(vty, "%% can't bind instance `%s' to port%s", src->name, VTY_NEWLINE); return CMD_WARNING; } INIT_LLIST_HEAD(&shared->conn_list); } route->shared = shared; src->refcnt++; route->shared->src.inst = src; dst->refcnt++; route->shared->dst.inst = dst; shared->src.streamid[src_streamid] = dst_streamid; shared->dst.streamid[dst_streamid] = src_streamid; ret = bitvec_set_bit_pos(&shared->src.streamid_map, src_streamid, ONE); if (ret < 0) { vty_out(vty, "%% bad bitmask (?)%s", VTY_NEWLINE); return CMD_WARNING; } ret = bitvec_set_bit_pos(&shared->dst.streamid_map, dst_streamid, ONE); if (ret < 0) { vty_out(vty, "%% bad bitmask (?)%s", VTY_NEWLINE); return CMD_WARNING; } shared->refcnt++; llist_add_tail(&route->head, &ipa_proxy_route_list); return CMD_SUCCESS; } DEFUN(ipa_route_add, ipa_route_add_cmd, "ipa route instance NAME streamid HEXNUM " "instance NAME streamid HEXNUM", "Add IPA route") { return __ipa_route_add(vty, argc, argv); } DEFUN(ipa_route_del, ipa_route_del_cmd, "no ipa route instance NAME streamid HEXNUM " "instance NAME streamid HEXNUM", "Delete IPA route") { struct ipa_proxy_instance *ipi = vty->index; struct ipa_proxy_instance *src = NULL, *dst = NULL; uint32_t src_streamid, dst_streamid; struct ipa_proxy_route *route, *matching_route = NULL; struct ipa_proxy_conn *conn, *tmp; if (argc < 4) return CMD_ERR_INCOMPLETE; llist_for_each_entry(ipi, &ipa_instance_list, head) { if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) == 0) { src = ipi; continue; } if (strncmp(ipi->name, argv[2], IPA_INSTANCE_NAME) == 0) { dst = ipi; continue; } } if (src == NULL) { vty_out(vty, "%% instance `%s' does not exists%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } if (dst == NULL) { vty_out(vty, "%% instance `%s' does not exists%s", argv[2], VTY_NEWLINE); return CMD_WARNING; } if (src->net.type != IPA_INSTANCE_T_BIND) { vty_out(vty, "%% instance `%s' is not of bind type%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } if (dst->net.type != IPA_INSTANCE_T_CONNECT) { vty_out(vty, "%% instance `%s' is not of connect type%s", argv[2], VTY_NEWLINE); return CMD_WARNING; } src_streamid = strtoul(argv[1], NULL, 16); if (src_streamid > 0xff) { vty_out(vty, "%% source streamid must be " ">= 0x00 and <= 0xff%s", VTY_NEWLINE); return CMD_WARNING; } dst_streamid = strtoul(argv[3], NULL, 16); if (dst_streamid > 0xff) { vty_out(vty, "%% destination streamid must be " ">= 0x00 and <= 0xff%s", VTY_NEWLINE); return CMD_WARNING; } llist_for_each_entry(route, &ipa_proxy_route_list, head) { if (route->shared->src.inst == src && route->shared->dst.inst == dst && route->src.streamid == src_streamid && route->dst.streamid == dst_streamid) { matching_route = route; break; } } if (matching_route == NULL) { vty_out(vty, "%% no route with that configuration%s", VTY_NEWLINE); return CMD_WARNING; } /* delete this route from list. */ llist_del(&matching_route->head); if (--matching_route->shared->refcnt == 0) { /* nobody else using this route, release all resources. */ llist_for_each_entry_safe(conn, tmp, &matching_route->shared->conn_list, head) { ipa_server_conn_destroy(conn->src); llist_del(&conn->head); talloc_free(conn); } osmo_fd_unregister(&route->shared->bfd); close(route->shared->bfd.fd); route->shared->bfd.fd = -1; talloc_free(route->shared); } else { /* otherwise, revert the mapping that this route applies. */ bitvec_set_bit_pos(&matching_route->shared->src.streamid_map, src_streamid, ZERO); bitvec_set_bit_pos(&matching_route->shared->dst.streamid_map, dst_streamid, ZERO); matching_route->shared->src.streamid[src_streamid] = 0x00; matching_route->shared->dst.streamid[dst_streamid] = 0x00; } matching_route->shared->src.inst->refcnt--; matching_route->shared->dst.inst->refcnt--; talloc_free(matching_route); return CMD_SUCCESS; } DEFUN(ipa_route_show, ipa_route_show_cmd, "ipa route show", "Show existing ipaccess proxy routes") { struct ipa_proxy_route *this; llist_for_each_entry(this, &ipa_proxy_route_list, head) { vty_out(vty, "route instance %s streamid 0x%.2x " "instance %s streamid 0x%.2x%s", this->shared->src.inst->name, this->src.streamid, this->shared->dst.inst->name, this->dst.streamid, VTY_NEWLINE); } return CMD_SUCCESS; } /* * Config for ipaccess-proxy */ DEFUN(ipa_cfg, ipa_cfg_cmd, "ipa", "Configure the ipaccess proxy") { vty->index = NULL; vty->node = L_IPA_NODE; return CMD_SUCCESS; } /* all these below look like enable commands, but without the ipa prefix. */ DEFUN(ipa_route_cfg_add, ipa_route_cfg_add_cmd, "route instance NAME streamid HEXNUM " "instance NAME streamid HEXNUM", "Add IPA route") { return __ipa_route_add(vty, argc, argv); } DEFUN(ipa_instance_cfg_add, ipa_instance_cfg_add_cmd, "instance NAME (bind|connect) IP tcp port PORT", "Bind or connect instance to address and port") { return __ipa_instance_add(vty, argc, argv); } struct cmd_node ipa_node = { L_IPA_NODE, "%s(config-ipa)# ", 1, }; static int ipa_cfg_write(struct vty *vty) { bool heading = false; struct ipa_proxy_instance *inst; struct ipa_proxy_route *route; llist_for_each_entry(inst, &ipa_instance_list, head) { if (!heading) { vty_out(vty, "ipa%s", VTY_NEWLINE); heading = true; } vty_out(vty, " instance %s %s %s tcp port %u%s", inst->name, inst->net.type == IPA_INSTANCE_T_BIND ? "bind" : "connect", inst->net.addr, inst->net.port, VTY_NEWLINE); } llist_for_each_entry(route, &ipa_proxy_route_list, head) { vty_out(vty, " route instance %s streamid 0x%.2x " "instance %s streamid 0x%.2x%s", route->shared->src.inst->name, route->src.streamid, route->shared->dst.inst->name, route->dst.streamid, VTY_NEWLINE); } return CMD_SUCCESS; } void ipa_proxy_vty_init(void) { tall_ipa_proxy_ctx = talloc_named_const(libosmo_abis_ctx, 1, "ipa_proxy"); install_lib_element(ENABLE_NODE, &ipa_instance_add_cmd); install_lib_element(ENABLE_NODE, &ipa_instance_del_cmd); install_lib_element(ENABLE_NODE, &ipa_instance_show_cmd); install_lib_element(ENABLE_NODE, &ipa_route_add_cmd); install_lib_element(ENABLE_NODE, &ipa_route_del_cmd); install_lib_element(ENABLE_NODE, &ipa_route_show_cmd); install_lib_element(CONFIG_NODE, &ipa_cfg_cmd); install_node(&ipa_node, ipa_cfg_write); install_lib_element(L_IPA_NODE, &ipa_instance_cfg_add_cmd); install_lib_element(L_IPA_NODE, &ipa_route_cfg_add_cmd); }