From 8bb1545e245e4546cb0bb6d1ee6dfa2b3106cf81 Mon Sep 17 00:00:00 2001 From: sup39 Date: Fri, 1 Jul 2022 10:50:40 +0900 Subject: [PATCH] init --- .gitignore | 3 + LICENSE | 24 ++++++ Makefile | 20 +++++ README.md | 11 +++ config.sample.lua | 47 +++++++++++ src/icmpv6.h | 92 +++++++++++++++++++++ src/options.c | 207 ++++++++++++++++++++++++++++++++++++++++++++++ src/options.h | 11 +++ src/ra.c | 56 +++++++++++++ src/ra.h | 18 ++++ src/supRA.c | 150 +++++++++++++++++++++++++++++++++ 11 files changed, 639 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 config.sample.lua create mode 100644 src/icmpv6.h create mode 100644 src/options.c create mode 100644 src/options.h create mode 100644 src/ra.c create mode 100644 src/ra.h create mode 100644 src/supRA.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d78a2d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +bin/ +config.lua diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ae591eb --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2022 sup39 + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6a12e7a --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +VERSION = 0.1.0-beta.1 +CC = gcc +CFLAGS = -O2 +LDFLAGS = -llua -lm + +IFACE = eth0 +CONFILE = config.lua + +.PHONY: run clean +all: bin/supRA +run: bin/supRA + @sudo $^ $(IFACE) $(CONFILE) +clean: + find . -name "*.o" | xargs rm -f + +bin/supRA: src/supRA.o src/ra.o src/options.o | bin + $(CC) -o $@ $^ $(LDFLAGS) + +bin: + mkdir -p $@ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7bf99b --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# supRA +A WIP customizable ICMPv6 Router Advertisement (RA) sender. + +[lua 5.4](https://www.lua.org/ftp/lua-5.4.4.tar.gz) is required. + +## Config +See [config.sample.lua](./config.sample.lua) for more information. + +## References +- [RFC4861 Router Advertisement Message Format](https://www.rfc-editor.org/rfc/rfc4861.html#section-4.2) +- [IANA IPv6 Neighbor Discovery Option Formats](https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5) diff --git a/config.sample.lua b/config.sample.lua new file mode 100644 index 0000000..4299443 --- /dev/null +++ b/config.sample.lua @@ -0,0 +1,47 @@ +-- https://www.rfc-editor.org/rfc/rfc4861.html#section-4.2 +ra { + -- hop = 64, + M = false, -- addresses are available via DHCPv6 + O = false, -- other configuration information is available via DHCPv6 + router_lft = 1800, -- router lifetime(s) + -- reachable_time = 0, + -- retrans_timer = 0, +} + +-- ra interval(ms) +interval = 600000 -- 10 minutes + +-- IPv6 address prefix +prefix '2001:db8:39:efbc::/62' { + L = true, -- on link + A = true, -- auto config address + -- valid_lft = -1, -- valid lifetime(s) (-1=infinity) + preferred_lft = 3600, -- preferred lifetime(s) +} +prefix '2001:db8:1207::/56' { + L = true, -- on link + A = false, -- auto config address + -- valid_lft = -1, -- valid lifetime(s) (-1=infinity) + -- preferred_lft = -1, -- preferred lifetime(s) (-1=infinity) +} + +-- route +route '2001:db8:426::/55' { + prf = 'M', -- preference (M/L/H) + lft = 1800, -- lifetime(s) +} +route '2001:db8:428:abcd::/64' { + prf = 'H', -- preference (M/L/H) + lft = 900, -- lifetime(s) +} + +-- dns +dns { + -- lifetime(s) + lft = 1800, -- 30 minutes + -- server 1 + '2001:db8:d25::53', + -- server 2 + '2001:db8:d25::5353', + -- other servers... +} diff --git a/src/icmpv6.h b/src/icmpv6.h new file mode 100644 index 0000000..0d980ce --- /dev/null +++ b/src/icmpv6.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 sup39 */ + +#ifndef sup_ICMPV6_H +#define sup_ICMPV6_H +#include +#include + +struct icmpv6_head { + uint8_t type; + uint8_t code; + uint16_t checksum; +}; +#define ICMPV6_RS 133 +#define ICMPV6_RA 134 +#define ICMPV6_OPT_SRCLINKADDR 1 +#define ICMPV6_OPT_TGTLINKADDR 2 +#define ICMPV6_OPT_PFXINFO 3 +#define ICMPV6_OPT_PFXINFO_LEN 4 +#define ICMPV6_OPT_MTU 5 +#define ICMPV6_OPT_MTU_LEN 1 +#define ICMPV6_OPT_ADVINTVL 7 +#define ICMPV6_OPT_ADVINTVL_LEN 1 +#define ICMPV6_OPT_RTINFO 24 +#define ICMPV6_OPT_RTINFO_LEN 3 +#define ICMPV6_OPT_DNSINFO 25 +struct icmpv6_rs { + uint32_t _rsvd; +}; +#define ICMPV6_RA_M 0x80 +#define ICMPV6_RA_O 0x40 +#define ICMPV6_RA_H 0x20 +#define ICMPV6_RA_PRF_H 0x08 +#define ICMPV6_RA_PRF_M 0x00 +#define ICMPV6_RA_PRF_L 0x18 +#define ICMPV6_RA_P 0x04 +struct icmpv6_ra { + uint8_t hop; + uint8_t flags; + uint16_t router_lft; + uint32_t reachable_time; + uint32_t retrans_timer; +}; +struct icmpv6_option { + uint8_t type; + uint8_t len; +}; +struct icmpv6_linkaddr { + uint8_t type; + uint8_t len; + uint8_t addr[6]; +}; +#define ICMPV6_PFXINFO_L 0x80 +#define ICMPV6_PFXINFO_A 0x40 +struct icmpv6_pfxinfo { + uint8_t type; + uint8_t len; + uint8_t pfxlen; + uint8_t flags; + uint32_t valid_lft; + uint32_t preferred_lft; + uint32_t _rsvd2; + struct in6_addr prefix; +}; +struct icmpv6_mtu { + uint8_t type; + uint8_t len; + uint16_t _rsvd; + uint32_t mtu; +}; +struct icmpv6_advintvl { + uint8_t type; + uint8_t len; + uint16_t _rsvd; + uint32_t interval; +}; +struct icmpv6_rtinfo { + uint8_t type; + uint8_t len; + uint8_t pfxlen; + uint8_t flags; + uint32_t lft; + struct in6_addr prefix; +}; +struct icmpv6_dnsinfo { + uint8_t type; + uint8_t len; + uint16_t _rsvd; + uint32_t lft; +}; + +#endif diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..fab9a33 --- /dev/null +++ b/src/options.c @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 sup39 */ + +#include +#include +#include +#include +#include +#include +#include "icmpv6.h" +#include "ra.h" + +#define PREPARE_get_int() \ + lua_Integer val; \ + int isnum; +#define get_int_field(key, dft) (\ + lua_getfield(L, -1, key), \ + val = lua_tointegerx(L, -1, &isnum), \ + lua_pop(L, 1), \ + isnum ? val : dft ) +#define has_int_field(key) (\ + lua_getfield(L, -1, key), \ + val = lua_tointegerx(L, -1, &isnum), \ + lua_pop(L, 1), \ + isnum) +#define get_flag_field(key, lhs, rhs) \ + lua_getfield(L, -1, key); \ + if (lua_toboolean(L, -1)) lhs |= rhs; \ + lua_pop(L, 1); + +#define has_int_global(key) (\ + lua_getglobal(L, key), \ + val = lua_tointegerx(L, -1, &isnum), \ + lua_pop(L, 1), \ + isnum) + +static void suplua_parse_prefix(lua_State *L, const char *raw, struct in6_addr *addr, uint8_t *pfxlen) { + /* parse */ + char *ptrSlash = strchr(raw, '/'); + if (ptrSlash == NULL) + luaL_error(L, "Prefix length is required"); + size_t strlenpfx = ptrSlash-raw; + if (strlenpfx >= INET6_ADDRSTRLEN) + luaL_error(L, "Invalid prefix: %s", raw); + // parse prefix + char strpfx[INET6_ADDRSTRLEN]; + strncpy(strpfx, raw, strlenpfx); + strpfx[strlenpfx] = '\0'; + if (inet_pton(AF_INET6, strpfx, addr) <= 0) + luaL_error(L, "Invalid prefix: %s", raw); + // parse prefix len + char *badlen = NULL; + *pfxlen = strtol(ptrSlash+1, &badlen, 10); + if (*badlen != '\0') + luaL_error(L, "Bad prefix length: %s", badlen); +} + +static int luaF_prefix_opt(lua_State *L) { + int n = lua_gettop(L); + if (!(n == 1 && lua_istable(L, 1))) + luaL_error(L, "Invalid prefix options\nSyntax: prefix 'PREFIX/LEN' {OPTIONS}"); + struct icmpv6_pfxinfo *pfx = lua_touserdata(L, lua_upvalueindex(1)); + lua_pushvalue(L, 1); // options + PREPARE_get_int(); + /* options */ + get_flag_field("A", pfx->flags, ICMPV6_PFXINFO_A); + get_flag_field("L", pfx->flags, ICMPV6_PFXINFO_L); + if (has_int_field("valid_lft")) pfx->valid_lft = htonl(val); + if (has_int_field("preferred_lft")) pfx->preferred_lft = htonl(val); + /* done */ + return 0; +} +static int luaF_prefix(lua_State *L) { + int n = lua_gettop(L); + if (!(n == 1 && lua_isstring(L, 1))) + return luaL_error(L, "Invalid prefix: %s\nSyntax: prefix 'PREFIX/LEN' {OPTIONS}", lua_tostring(L, 1)); + const char *raw = lua_tostring(L, 1); + // init pfx + ALLOC_RA_OPTION(struct icmpv6_pfxinfo, pfx); + pfx->type = ICMPV6_OPT_PFXINFO; + pfx->len = ICMPV6_OPT_PFXINFO_LEN; + pfx->flags = 0; + pfx->valid_lft = htonl(-1); + pfx->preferred_lft = htonl(-1); + pfx->_rsvd2 = 0; + suplua_parse_prefix(L, raw, &pfx->prefix, &pfx->pfxlen); + // return + lua_pushlightuserdata(L, pfx); + lua_pushcclosure(L, luaF_prefix_opt, 1); + return 1; +} + +static int luaF_route_opt(lua_State *L) { + int n = lua_gettop(L); + if (!(n == 1 && lua_istable(L, 1))) + luaL_error(L, "Invalid route options\nSyntax: route 'PREFIX/LEN' {OPTIONS}"); + struct icmpv6_rtinfo *rt = lua_touserdata(L, lua_upvalueindex(1)); + lua_pushvalue(L, 1); // options + /* options */ + PREPARE_get_int(); + if(has_int_field("lft")) rt->lft = htonl(val); + // prf + lua_getfield(L, -1, "prf"); + if (lua_isinteger(L, -1)) { + rt->flags = (lua_tointeger(L, -1)&3)<<3; + } else if (lua_isstring(L, -1)) { + const char *strprf = lua_tostring(L, -1); + if (*strprf == 'L') rt->flags = ICMPV6_RA_PRF_L; + else if (*strprf == 'H') rt->flags = ICMPV6_RA_PRF_H; + } + /* done */ + return 0; +} +static int luaF_route(lua_State *L) { + int n = lua_gettop(L); + if (!(n == 1 && lua_isstring(L, 1))) + return luaL_error(L, "Invalid route\nSyntax: route 'PREFIX/LEN' {OPTIONS}"); + const char *raw = lua_tostring(L, 1); + // init pfx + ALLOC_RA_OPTION(struct icmpv6_rtinfo, rt); + rt->type = ICMPV6_OPT_RTINFO; + rt->len = ICMPV6_OPT_RTINFO_LEN; + rt->flags = ICMPV6_RA_PRF_M; + rt->lft = htonl(1800); + suplua_parse_prefix(L, raw, &rt->prefix, &rt->pfxlen); + // return + lua_pushlightuserdata(L, rt); + lua_pushcclosure(L, luaF_prefix_opt, 1); + return 1; +} + +static int luaF_ra(lua_State *L) { + int n = lua_gettop(L); + if (!(n == 1 && lua_istable(L, 1))) + luaL_error(L, "Invalid ra options\nSyntax: ra {OPTIONS}"); + lua_pushvalue(L, 1); // options + PREPARE_get_int(); + /* options */ + struct icmpv6_ra *ra = get_ra_fields(); + if (has_int_field("hop")) ra->hop = val; + if (has_int_field("router_lft")) ra->router_lft = htons(val); + if (has_int_field("reachable_time")) ra->reachable_time = htonl(val); + if (has_int_field("retrans_timer")) ra->retrans_timer = htonl(val); + get_flag_field("M", ra->flags, ICMPV6_RA_M); + get_flag_field("O", ra->flags, ICMPV6_RA_O); + /* done */ + return 0; +} + +static int luaF_dns(lua_State *L) { + int n = lua_gettop(L); + if (!(n == 1 && lua_istable(L, 1))) + luaL_error(L, "Invalid dns options\nSyntax: dns {server1, server2, ..., lft = xxx}"); + lua_pushvalue(L, 1); // options + PREPARE_get_int(); + /* options */ + ALLOC_RA_OPTION(struct icmpv6_dnsinfo, dns); + dns->type = ICMPV6_OPT_DNSINFO; + dns->len = 1; + dns->_rsvd = 0; + dns->lft = htonl(get_int_field("lft", -1)); + // iterate dns server ip + lua_pushnil(L); // dummy key + while (lua_next(L, 1)) { // [-2]=key, [-1]=value + // key should be number + if (lua_isnumber(L, -2)) { + ALLOC_RA_OPTION(struct in6_addr, addr); + inet_pton(AF_INET6, lua_tostring(L, -1), addr); + dns->len += 2; // 128 bit = 8 bytes *2 + } + // pop value + lua_pop(L, 1); + } + /* done */ + return 0; +} + +#define lua_pushglobalfunc(L, name, f) (\ + lua_pushcfunction(L, f), \ + lua_setglobal(L, name)) +int supRA_read_option(const char *path) { + lua_State *L = luaL_newstate(); + + // prepare + lua_pushglobalfunc(L, "prefix", luaF_prefix); + lua_pushglobalfunc(L, "route", luaF_route); + lua_pushglobalfunc(L, "ra", luaF_ra); + lua_pushglobalfunc(L, "dns", luaF_dns); + + // read options + int rc = luaL_dofile(L, path); + if (rc != LUA_OK) + fprintf(stderr, "Fail to read options file (%d):\n%s\n", rc, lua_tostring(L, -1)); + + /* vars */ + PREPARE_get_int(); + // interval + if (has_int_global("interval")) { + ALLOC_RA_OPTION(struct icmpv6_advintvl, intvl); + intvl->type = ICMPV6_OPT_ADVINTVL; + intvl->len = ICMPV6_OPT_ADVINTVL_LEN; + intvl->_rsvd = 0; + intvl->interval = htonl(val); + } + + return rc; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..3eb92b6 --- /dev/null +++ b/src/options.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 sup39 */ + +#ifndef supRA_OPTIONS_H +#define supRA_OPTIONS_H +#include +#include + +int supRA_read_option(const char *path); + +#endif diff --git a/src/ra.c b/src/ra.c new file mode 100644 index 0000000..3de6bc5 --- /dev/null +++ b/src/ra.c @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 sup39 */ + +#include +#include +#include +#include +#include +#include "ra.h" +#include "icmpv6.h" + +static uint8_t + *ra_msg_buf = NULL, + *ra_msg_ptr = NULL, + *ra_msg_buflim = NULL; + +void init_ra_msg_buf(size_t bufsize) { + ra_msg_ptr = ra_msg_buf = malloc(bufsize); + ra_msg_buflim = ra_msg_buf+bufsize; + + /* payload header */ + ALLOC_RA_OPTION(struct icmpv6_head, hw); + hw->type = ICMPV6_RA; + hw->code = 0; + hw->checksum = 0; + + /* message header */ + ALLOC_RA_OPTION(struct icmpv6_ra, ra); + ra->hop = 64; + ra->flags = 0; + ra->router_lft = htons(1800); + ra->reachable_time = htonl(0); + ra->retrans_timer = htonl(0); +} + +void *alloc_ra_option(size_t size) { + void *ptr = ra_msg_ptr; + ra_msg_ptr += size; + if (ra_msg_ptr > ra_msg_buflim) { + fputs("Message is too long", stderr); + exit(EMSGSIZE); + } + return ptr; +} + +struct msg_header { + struct icmpv6_head head; + struct icmpv6_ra ra; +}; +struct icmpv6_ra *get_ra_fields() { + return &((struct msg_header*)ra_msg_buf)->ra; +} + +ssize_t send_ra(int fd, struct sockaddr *caddr, socklen_t caddrlen) { + return sendto(fd, ra_msg_buf, ra_msg_ptr-ra_msg_buf, 0, caddr, caddrlen); +} diff --git a/src/ra.h b/src/ra.h new file mode 100644 index 0000000..f4953bf --- /dev/null +++ b/src/ra.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 sup39 */ + +#ifndef supRA_RA_H +#define supRA_RA_H +#include +#include +#include + +void init_ra_msg_buf(size_t bufsize); +void *alloc_ra_option(size_t size); +#define ALLOC_RA_OPTION(TYPE, NAME) TYPE *NAME = alloc_ra_option(sizeof(TYPE)) +#define ALLOC_RA_OPTION_(TYPE, NAME) NAME = alloc_ra_option(sizeof(TYPE)) + +struct icmpv6_ra *get_ra_fields(); +ssize_t send_ra(int fd, struct sockaddr *caddr, socklen_t caddrlen); + +#endif diff --git a/src/supRA.c b/src/supRA.c new file mode 100644 index 0000000..bd571c6 --- /dev/null +++ b/src/supRA.c @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 sup39 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "icmpv6.h" +#include "ra.h" +#include "options.h" + +#define NEXT_STRUCT(s, p) (struct s*)p; p += sizeof(struct s) +#define DEF_NEXT_STRUCT(s, v, p) struct s *v = NEXT_STRUCT(s, p); + +static void init_ra_msg(int if_mtu, uint8_t if_macaddr[6]) { + /** link addr **/ + ALLOC_RA_OPTION(struct icmpv6_linkaddr, linkaddr); + linkaddr->type = ICMPV6_OPT_SRCLINKADDR; + linkaddr->len = 1; + memcpy(linkaddr->addr, if_macaddr, 6); + + /** MTU **/ + ALLOC_RA_OPTION(struct icmpv6_mtu, mtu); + mtu->type = ICMPV6_OPT_MTU; + mtu->len = ICMPV6_OPT_MTU_LEN; + mtu->_rsvd = 0; + mtu->mtu = htonl(if_mtu); +} + +#define PERROR_EXIT(msg) {perror(msg); exit(errno);} +int main(int argc, char *argv[]) { + if (argc <= 2) { + fprintf(stderr, "Usage: %s IFNAME CONFIG.lua\n", argv[0]); + return 1; + } + + const char *ifname = argv[1]; + int ifid = if_nametoindex(ifname); + if (ifid == 0) PERROR_EXIT("Bad Interface"); + + /** get link info (man netdevice) **/ + int if_mtu; + uint8_t if_macaddr[6]; + struct ifreq ifr; + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + int iofd = socket(AF_INET6, SOCK_DGRAM, 0); + if (iofd < 0) PERROR_EXIT("Fail to create socket"); + // macaddr + if (ioctl(iofd, SIOCGIFHWADDR, &ifr)) PERROR_EXIT("Fail to ioctl(HWADDR)"); + memcpy(if_macaddr, &ifr.ifr_hwaddr.sa_data, sizeof(if_macaddr)); + // mtu + if (if_mtu <= 0) { + if (ioctl(iofd, SIOCGIFMTU, &ifr)) PERROR_EXIT("Fail to ioctl(MTU)"); + if_mtu = ifr.ifr_mtu; + } + // clean + close(iofd); + + int sfd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (sfd < 0) PERROR_EXIT("Fail to create socket"); + + /* prepare ra message */ + init_ra_msg_buf(if_mtu-40); // sizeof ICMPv6 header = 40 + init_ra_msg(if_mtu, if_macaddr); + if (supRA_read_option(argv[2])) return 1; + + /** join ff02::2 **/ + struct ipv6_mreq mreq; + int hoplimit; + inet_pton(AF_INET6, "ff02::2", &mreq.ipv6mr_multiaddr); + mreq.ipv6mr_interface = ifid; + if (setsockopt(sfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + PERROR_EXIT("Fail to setsockopt(ADD_MEMBERSHIP)"); + hoplimit = 255; + if (setsockopt(sfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hoplimit, sizeof(hoplimit)) < 0) + PERROR_EXIT("Fail to setsockopt(HOPS)"); + if (setsockopt(sfd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hoplimit, sizeof(hoplimit)) < 0) + PERROR_EXIT("Fail to setsockopt(HOPS)"); + if (setsockopt(sfd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0) + PERROR_EXIT("Fail to setsockopt(BINDTODEVICE)"); + + struct icmp6_filter filter; + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); + if (setsockopt(sfd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) < 0) + PERROR_EXIT("Fail to setsockopt(ICMP6_FILTER)"); + + /** ff02::1 **/ + struct sockaddr_in6 bcaddr; + bcaddr.sin6_family = AF_INET6; + bcaddr.sin6_port = 0; + inet_pton(AF_INET6, "ff02::1", &bcaddr.sin6_addr); + bcaddr.sin6_scope_id = ifid; + socklen_t bcaddrlen = sizeof(bcaddr); + + /** epoll **/ + int epfd = epoll_create1(0); + if (epfd < 0) PERROR_EXIT("Fail to create epoll"); + struct epoll_event ev = { + .events = EPOLLIN, + .data.fd = sfd, + }; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev)) + PERROR_EXIT("Fail to epoll_ctl(ADD)"); + const int evcnt = 1; + struct epoll_event evs[evcnt]; + + /** loop **/ + srand(time(NULL)); + time_t tnext = time(NULL); + while (1) { + time_t now = time(NULL); + if (now >= tnext) { // advertise right now + send_ra(sfd, (struct sockaddr*)&bcaddr, bcaddrlen); + tnext = now + 200 + 400*rand()/RAND_MAX; // TODO + puts("to ff02::1"); + } + + int evc = epoll_wait(epfd, evs, evcnt, tnext-now); + if (evc < 0) PERROR_EXIT("Fail to epoll_wait"); + while (evc--) { + uint32_t fd = evs[evc].data.fd; + uint8_t buf[4096]; + char caddr_p[INET6_ADDRSTRLEN]; + struct sockaddr_in6 caddr; + socklen_t caddrlen = sizeof(caddr); + ssize_t buflen = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&caddr, &caddrlen); + if (buflen < 0) PERROR_EXIT("Fail to recvfrom()"); + if (buflen < sizeof(struct icmpv6_head)) continue; // bad message + // if (caddr.sin6_scope_id != ifid) continue; + inet_ntop(AF_INET6, &caddr.sin6_addr, caddr_p, sizeof(caddr_p)); + // struct icmpv6_head *h = (struct icmpv6_head*)buf; + send_ra(sfd, (struct sockaddr*)&caddr, caddrlen); + printf("to %s%%%d\n", caddr_p, caddr.sin6_scope_id); + } + } + return 0; +}