init
This commit is contained in:
commit
8bb1545e24
11 changed files with 639 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
*.o
|
||||||
|
bin/
|
||||||
|
config.lua
|
24
LICENSE
Normal file
24
LICENSE
Normal 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
20
Makefile
Normal 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
11
README.md
Normal 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
47
config.sample.lua
Normal 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
92
src/icmpv6.h
Normal 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
207
src/options.c
Normal 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
11
src/options.h
Normal 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
56
src/ra.c
Normal 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
18
src/ra.h
Normal 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
150
src/supRA.c
Normal 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;
|
||||||
|
}
|
Loading…
Reference in a new issue