123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
- */
- #define _GNU_SOURCE
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netlink/msg.h>
- #include <netlink/attr.h>
- #include <netlink/socket.h>
- #include <netinet/if_ether.h>
- #include <net/if.h>
- #include <linux/rtnetlink.h>
- #include <linux/ipv6.h>
- #include <linux/udp.h>
- #include "unetd.h"
- struct vxlan_tunnel {
- struct network *net;
- struct network_service *s;
- char ifname[IFNAMSIZ + 1];
- int ifindex;
- uint16_t mtu;
- uint16_t port;
- uint32_t vni;
- uint32_t *forward_ports;
- uint32_t *cur_forward_ports;
- bool active;
- };
- static uint32_t
- vxlan_tunnel_id(struct vxlan_tunnel *vt)
- {
- siphash_key_t key = {};
- const char *name = network_service_name(vt->s);
- uint64_t val;
- if (vt->vni != ~0)
- return vt->vni;
- siphash_to_le64(&val, name, strlen(name), &key);
- return val & 0x00ffffff;
- }
- static struct nl_msg *vxlan_rtnl_msg(const char *ifname, int type, int flags)
- {
- struct ifinfomsg iim = {
- .ifi_family = AF_UNSPEC,
- };
- struct nl_msg *msg;
- msg = nlmsg_alloc_simple(type, flags | NLM_F_REQUEST);
- if (!msg)
- return NULL;
- nlmsg_append(msg, &iim, sizeof(iim), 0);
- nla_put_string(msg, IFLA_IFNAME, ifname);
- return msg;
- }
- static int
- vxlan_update_host_fdb_entry(struct vxlan_tunnel *vt, struct network_host *host, bool add)
- {
- struct ndmsg ndmsg = {
- .ndm_family = PF_BRIDGE,
- .ndm_state = NUD_NOARP | NUD_PERMANENT,
- .ndm_flags = NTF_SELF,
- .ndm_ifindex = vt->ifindex,
- };
- unsigned int flags = NLM_F_REQUEST;
- uint8_t lladdr[ETH_ALEN] = {};
- struct nl_msg *msg;
- if (add)
- flags |= NLM_F_CREATE | NLM_F_APPEND;
- msg = nlmsg_alloc_simple(add ? RTM_NEWNEIGH : RTM_DELNEIGH, flags);
- nlmsg_append(msg, &ndmsg, sizeof(ndmsg), 0);
- nla_put(msg, NDA_LLADDR, ETH_ALEN, lladdr);
- nla_put(msg, NDA_DST, sizeof(struct in6_addr), &host->peer.local_addr);
- nla_put_u32(msg, NDA_IFINDEX, vt->net->ifindex);
- return rtnl_call(msg);
- }
- static void
- vxlan_update_fdb_hosts(struct vxlan_tunnel *vt)
- {
- struct network_service *s = vt->s;
- bool active;
- int i;
- if (!vt->active)
- return;
- for (i = 0; i < s->n_members; i++) {
- if (s->members[i] == vt->net->net_config.local_host)
- continue;
- if (vt->forward_ports && !bitmask_test(vt->forward_ports, i))
- continue;
- active = s->members[i]->peer.state.connected;
- if (active == bitmask_test(vt->cur_forward_ports, i))
- continue;
- if (!vxlan_update_host_fdb_entry(vt, s->members[i], active))
- bitmask_set_val(vt->cur_forward_ports, i, active);
- }
- }
- static void
- vxlan_peer_update(struct network *net, struct network_service *s, struct network_peer *peer)
- {
- if (!s->vxlan)
- return;
- vxlan_update_fdb_hosts(s->vxlan);
- }
- static void
- vxlan_tunnel_init(struct vxlan_tunnel *vt)
- {
- struct network_peer *local = &vt->net->net_config.local_host->peer;
- struct nlattr *linkinfo, *data;
- struct nl_msg *msg;
- struct in6_addr group_addr;
- int mtu;
- if (rtnl_init())
- return;
- memset(&group_addr, 0xff, sizeof(group_addr));
- msg = vxlan_rtnl_msg(vt->ifname, RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL);
- linkinfo = nla_nest_start(msg, IFLA_LINKINFO);
- nla_put_string(msg, IFLA_INFO_KIND, "vxlan");
- nla_put_u32(msg, IFLA_MTU, vt->mtu);
- data = nla_nest_start(msg, IFLA_INFO_DATA);
- nla_put_u32(msg, IFLA_VXLAN_ID, vxlan_tunnel_id(vt));
- nla_put(msg, IFLA_VXLAN_LOCAL6, sizeof(struct in6_addr), &local->local_addr);
- nla_put(msg, IFLA_VXLAN_GROUP6, sizeof(struct in6_addr), &group_addr);
- nla_put_u16(msg, IFLA_VXLAN_PORT, htons(vt->port));
- nla_put_u8(msg, IFLA_VXLAN_LEARNING, 1);
- nla_put_u32(msg, IFLA_VXLAN_LINK, vt->net->ifindex);
- nla_nest_end(msg, data);
- nla_nest_end(msg, linkinfo);
- if (rtnl_call(msg) < 0)
- return;
- vt->ifindex = if_nametoindex(vt->ifname);
- if (!vt->ifindex) {
- D_SERVICE(vt->net, vt->s, "failed to get ifindex for device %s", vt->ifname);
- return;
- }
- vt->active = true;
- vxlan_update_fdb_hosts(vt);
- mtu = 1420 - sizeof(struct ipv6hdr) - sizeof(struct udphdr) - 8;
- unetd_attach_mssfix(vt->ifindex, mtu);
- }
- static void
- vxlan_tunnel_teardown(struct vxlan_tunnel *vt)
- {
- struct nl_msg *msg;
- vt->active = false;
- msg = vxlan_rtnl_msg(vt->ifname, RTM_DELLINK, 0);
- rtnl_call(msg);
- }
- static const char *
- vxlan_find_ifname(struct network *net, const char *service)
- {
- struct blob_attr *cur;
- int rem;
- if (!net->config.tunnels)
- return NULL;
- blobmsg_for_each_attr(cur, net->config.tunnels, rem) {
- const char *name;
- if (!blobmsg_check_attr(cur, true) ||
- blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
- continue;
- if (strcmp(blobmsg_get_string(cur), service) != 0)
- continue;
- name = blobmsg_name(cur);
- if (strlen(name) > IFNAMSIZ)
- break;
- return name;
- }
- return NULL;
- }
- static void
- __vxlan_mark_forward_host(struct vxlan_tunnel *vt, struct network_host *host)
- {
- struct network_service *s = vt->s;
- unsigned int i;
- for (i = 0; i < s->n_members; i++) {
- if (s->members[i] != host)
- continue;
- bitmask_set(vt->forward_ports, i);
- break;
- }
- }
- static void
- vxlan_mark_forward_host(struct vxlan_tunnel *vt, const char *name)
- {
- struct network *net = vt->net;
- struct network_host *host;
- host = avl_find_element(&net->hosts, name, host, node);
- if (!host)
- return;
- __vxlan_mark_forward_host(vt, host);
- }
- static void
- vxlan_mark_forward_group(struct vxlan_tunnel *vt, const char *name)
- {
- struct network *net = vt->net;
- struct network_group *group;
- int i;
- group = avl_find_element(&net->groups, name, group, node);
- if (!group)
- return;
- for (i = 0; i < group->n_members; i++)
- __vxlan_mark_forward_host(vt, group->members[i]);
- }
- static void
- vxlan_init_forward_ports(struct vxlan_tunnel *vt, struct blob_attr *data)
- {
- unsigned int len = bitmask_size(vt->s->n_members);
- struct blob_attr *cur;
- int rem;
- vt->cur_forward_ports = realloc(vt->cur_forward_ports, len);
- memset(vt->cur_forward_ports, 0, len);
- if (!data || blobmsg_check_array(data, BLOBMSG_TYPE_STRING) <= 0) {
- free(vt->forward_ports);
- vt->forward_ports = NULL;
- return;
- }
- vt->forward_ports = realloc(vt->forward_ports, len);
- memset(vt->forward_ports, 0, len);
- blobmsg_for_each_attr(cur, data, rem) {
- const char *name = blobmsg_get_string(cur);
- if (name[0] == '@')
- vxlan_mark_forward_group(vt, name + 1);
- else
- vxlan_mark_forward_host(vt, name);
- }
- }
- static bool
- vxlan_config_equal(struct network_service *s1, struct network_service *s2)
- {
- int i;
- if (!blob_attr_equal(s1->config, s2->config))
- return false;
- if (s1->n_members != s2->n_members)
- return false;
- for (i = 0; i < s1->n_members; i++)
- if (memcmp(s1->members[i]->peer.key, s2->members[i]->peer.key,
- CURVE25519_KEY_SIZE) != 0)
- return false;
- return true;
- }
- static void
- vxlan_init(struct network *net, struct network_service *s,
- struct network_service *s_old)
- {
- enum {
- VXCFG_ATTR_FWD_PORTS,
- VXCFG_ATTR_ID,
- VXCFG_ATTR_PORT,
- VXCFG_ATTR_MTU,
- __VXCFG_ATTR_MAX
- };
- static const struct blobmsg_policy policy[__VXCFG_ATTR_MAX] = {
- [VXCFG_ATTR_FWD_PORTS] = { "forward_ports", BLOBMSG_TYPE_ARRAY },
- [VXCFG_ATTR_ID] = { "id", BLOBMSG_TYPE_INT32 },
- [VXCFG_ATTR_PORT] = { "port", BLOBMSG_TYPE_INT32 },
- [VXCFG_ATTR_MTU] = { "mtu", BLOBMSG_TYPE_INT32 },
- };
- struct blob_attr *tb[__VXCFG_ATTR_MAX] = {};
- struct blob_attr *cur;
- struct vxlan_tunnel *vt = s->vxlan;
- const char *name;
- if (s_old) {
- vt = s_old->vxlan;
- s_old->vxlan = NULL;
- if (!vt)
- return;
- if (vxlan_config_equal(s, s_old)) {
- s->vxlan = vt;
- vt->s = s;
- return;
- }
- vxlan_tunnel_teardown(vt);
- goto init;
- }
- name = vxlan_find_ifname(net, network_service_name(s));
- if (!name) {
- D_SERVICE(net, s, "no configured tunnel ifname");
- return;
- }
- vt = calloc(1, sizeof(*s->vxlan));
- snprintf(vt->ifname, sizeof(vt->ifname), "%s", name);
- vt->net = net;
- init:
- s->vxlan = vt;
- vt->s = s;
- if (s->config)
- blobmsg_parse(policy, __VXCFG_ATTR_MAX, tb, blobmsg_data(s->config),
- blobmsg_len(s->config));
- vxlan_init_forward_ports(vt, tb[VXCFG_ATTR_FWD_PORTS]);
- if ((cur = tb[VXCFG_ATTR_ID]) != NULL)
- vt->vni = blobmsg_get_u32(cur) & 0x00ffffff;
- else
- vt->vni = ~0;
- if ((cur = tb[VXCFG_ATTR_PORT]) != NULL)
- vt->port = blobmsg_get_u32(cur);
- else
- vt->port = 4789;
- if ((cur = tb[VXCFG_ATTR_MTU]) != NULL)
- vt->mtu = blobmsg_get_u32(cur);
- else
- vt->mtu = 1500;
- vxlan_tunnel_init(vt);
- }
- static void
- vxlan_free(struct network *net, struct network_service *s)
- {
- struct vxlan_tunnel *vt = s->vxlan;
- if (!vt)
- return;
- vxlan_tunnel_teardown(vt);
- s->vxlan = NULL;
- free(vt->forward_ports);
- free(vt);
- }
- const struct service_ops vxlan_ops = {
- .init = vxlan_init,
- .free = vxlan_free,
- .peer_update = vxlan_peer_update,
- };
|