This commit is contained in:
sup39 2022-07-01 10:50:40 +09:00
commit 8bb1545e24
11 changed files with 639 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.o
bin/
config.lua

24
LICENSE Normal file
View file

@ -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.

20
Makefile Normal file
View file

@ -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 $@

11
README.md Normal file
View file

@ -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)

47
config.sample.lua Normal file
View file

@ -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...
}

92
src/icmpv6.h Normal file
View file

@ -0,0 +1,92 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2022 sup39 */
#ifndef sup_ICMPV6_H
#define sup_ICMPV6_H
#include <netinet/in.h>
#include <stdint.h>
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

207
src/options.c Normal file
View file

@ -0,0 +1,207 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2022 sup39 */
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <lua.h>
#include <lauxlib.h>
#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;
}

11
src/options.h Normal file
View file

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2022 sup39 */
#ifndef supRA_OPTIONS_H
#define supRA_OPTIONS_H
#include <stdlib.h>
#include <stdint.h>
int supRA_read_option(const char *path);
#endif

56
src/ra.c Normal file
View file

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2022 sup39 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#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);
}

18
src/ra.h Normal file
View file

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2022 sup39 */
#ifndef supRA_RA_H
#define supRA_RA_H
#include <stdlib.h>
#include <stdint.h>
#include <netinet/in.h>
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

150
src/supRA.c Normal file
View file

@ -0,0 +1,150 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2022 sup39 */
#include <stdio.h>
#include <string.h>
#include <net/if.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <netinet/icmp6.h>
#include <lua.h>
#include <lauxlib.h>
#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;
}