/* * seg6.c "ip sr/seg6" * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation; * * Author: David Lebrun <david.lebrun@uclouvain.be> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <linux/if.h> #include <linux/genetlink.h> #include <linux/seg6_genl.h> #include <linux/seg6_hmac.h> #include "utils.h" #include "ip_common.h" #include "libgenl.h" #define HMAC_KEY_PROMPT "Enter secret for HMAC key ID (blank to delete): " static void usage(void) { fprintf(stderr, "Usage: ip sr { COMMAND | help }\n"); fprintf(stderr, " ip sr hmac show\n"); fprintf(stderr, " ip sr hmac set KEYID ALGO\n"); fprintf(stderr, " ip sr tunsrc show\n"); fprintf(stderr, " ip sr tunsrc set ADDRESS\n"); fprintf(stderr, "where ALGO := { sha1 | sha256 }\n"); exit(-1); } static struct rtnl_handle grth = { .fd = -1 }; static int genl_family = -1; #define SEG6_REQUEST(_req, _bufsiz, _cmd, _flags) \ GENL_REQUEST(_req, _bufsiz, genl_family, 0, \ SEG6_GENL_VERSION, _cmd, _flags) static struct { unsigned int cmd; struct in6_addr addr; __u32 keyid; const char *pass; __u8 alg_id; } opts; static int process_msg(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) { struct rtattr *attrs[SEG6_ATTR_MAX + 1]; struct genlmsghdr *ghdr; FILE *fp = (FILE *)arg; int len = n->nlmsg_len; if (n->nlmsg_type != genl_family) return -1; len -= NLMSG_LENGTH(GENL_HDRLEN); if (len < 0) return -1; ghdr = NLMSG_DATA(n); parse_rtattr(attrs, SEG6_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len); switch (ghdr->cmd) { case SEG6_CMD_DUMPHMAC: { char secret[64]; char *algstr; __u8 slen = rta_getattr_u8(attrs[SEG6_ATTR_SECRETLEN]); __u8 alg_id = rta_getattr_u8(attrs[SEG6_ATTR_ALGID]); memset(secret, 0, 64); if (slen > 63) { fprintf(stderr, "HMAC secret length %d > 63, " "truncated\n", slen); slen = 63; } memcpy(secret, RTA_DATA(attrs[SEG6_ATTR_SECRET]), slen); switch (alg_id) { case SEG6_HMAC_ALGO_SHA1: algstr = "sha1"; break; case SEG6_HMAC_ALGO_SHA256: algstr = "sha256"; break; default: algstr = "<unknown>"; } fprintf(fp, "hmac %u ", rta_getattr_u32(attrs[SEG6_ATTR_HMACKEYID])); fprintf(fp, "algo %s ", algstr); fprintf(fp, "secret \"%s\" ", secret); fprintf(fp, "\n"); break; } case SEG6_CMD_GET_TUNSRC: { fprintf(fp, "tunsrc addr %s\n", rt_addr_n2a(AF_INET6, 16, RTA_DATA(attrs[SEG6_ATTR_DST]))); break; } } return 0; } static int seg6_do_cmd(void) { SEG6_REQUEST(req, 1024, opts.cmd, NLM_F_REQUEST); int repl = 0, dump = 0; if (genl_family < 0) { if (rtnl_open_byproto(&grth, 0, NETLINK_GENERIC) < 0) { fprintf(stderr, "Cannot open generic netlink socket\n"); exit(1); } genl_family = genl_resolve_family(&grth, SEG6_GENL_NAME); if (genl_family < 0) exit(1); req.n.nlmsg_type = genl_family; } switch (opts.cmd) { case SEG6_CMD_SETHMAC: { addattr32(&req.n, sizeof(req), SEG6_ATTR_HMACKEYID, opts.keyid); addattr8(&req.n, sizeof(req), SEG6_ATTR_SECRETLEN, strlen(opts.pass)); addattr8(&req.n, sizeof(req), SEG6_ATTR_ALGID, opts.alg_id); if (strlen(opts.pass)) addattr_l(&req.n, sizeof(req), SEG6_ATTR_SECRET, opts.pass, strlen(opts.pass)); break; } case SEG6_CMD_SET_TUNSRC: addattr_l(&req.n, sizeof(req), SEG6_ATTR_DST, &opts.addr, sizeof(struct in6_addr)); break; case SEG6_CMD_DUMPHMAC: dump = 1; break; case SEG6_CMD_GET_TUNSRC: repl = 1; break; } if (!repl && !dump) { if (rtnl_talk(&grth, &req.n, NULL, 0) < 0) return -1; } else if (repl) { if (rtnl_talk(&grth, &req.n, &req.n, sizeof(req)) < 0) return -2; if (process_msg(NULL, &req.n, stdout) < 0) { fprintf(stderr, "Error parsing reply\n"); exit(1); } } else { req.n.nlmsg_flags |= NLM_F_DUMP; req.n.nlmsg_seq = grth.dump = ++grth.seq; if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) { perror("Failed to send dump request"); exit(1); } if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) { fprintf(stderr, "Dump terminated\n"); exit(1); } } return 0; } int do_seg6(int argc, char **argv) { if (argc < 1 || matches(*argv, "help") == 0) usage(); memset(&opts, 0, sizeof(opts)); if (matches(*argv, "hmac") == 0) { NEXT_ARG(); if (matches(*argv, "show") == 0) { opts.cmd = SEG6_CMD_DUMPHMAC; } else if (matches(*argv, "set") == 0) { NEXT_ARG(); if (get_u32(&opts.keyid, *argv, 0) || opts.keyid == 0) invarg("hmac KEYID value is invalid", *argv); NEXT_ARG(); if (strcmp(*argv, "sha1") == 0) { opts.alg_id = SEG6_HMAC_ALGO_SHA1; } else if (strcmp(*argv, "sha256") == 0) { opts.alg_id = SEG6_HMAC_ALGO_SHA256; } else { invarg("hmac ALGO value is invalid", *argv); } opts.cmd = SEG6_CMD_SETHMAC; #ifndef __BIONIC__ opts.pass = getpass(HMAC_KEY_PROMPT); #endif } else { invarg("unknown", *argv); } } else if (matches(*argv, "tunsrc") == 0) { NEXT_ARG(); if (matches(*argv, "show") == 0) { opts.cmd = SEG6_CMD_GET_TUNSRC; } else if (matches(*argv, "set") == 0) { NEXT_ARG(); opts.cmd = SEG6_CMD_SET_TUNSRC; if (!inet_get_addr(*argv, NULL, &opts.addr)) invarg("tunsrc ADDRESS value is invalid", *argv); } else { invarg("unknown", *argv); } } else { invarg("unknown", *argv); } return seg6_do_cmd(); }