diff --git a/README.md b/README.md index bdf04a2..1bda388 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # ipv6-global-ip-demo A simple web application to test whether your ISP provides you 2^64 ipv6 global IP or not + +## Usage + +* clone the repo +* run `npm install` or `yarn install` +* export environment variable `ETH_DEV` which points to ether net interface with public IPv6 address. +* Run server . Eg: `env ETH_DEV=em1 npm run server`, where em1 is the ether net interface with global IPv6 IP. +* Open [http://127.0.0.1:3553/] in the browser to access the application diff --git a/index.js b/index.js index b9b253f..b3968ac 100644 --- a/index.js +++ b/index.js @@ -6,16 +6,17 @@ const Koa = require('koa'); const Router = require('koa-joi-router'); const util = require('util'); +const {RateLimiterMemory} = require('rate-limiter-flexible'); + const app = new Koa(); const router = new Router(); const Joi = Router.Joi; const _exec = util.promisify( require('child_process').exec ); const ethDev = process.env.ETH_DEV -const {RateLimiterMemory} = require('rate-limiter-flexible'); const rateLimitOpts = { - points: 1, // 1 request for ctx.ip - duration: 1, // per 1 second + points: 5, // 1 request for ctx.ip + duration: 5, // per 1 second }; const rateLimiter = new RateLimiterMemory( rateLimitOpts ); const MyJoi = { @@ -23,7 +24,8 @@ const MyJoi = { return Joi.string().ip({ version: [ 'ipv6' - ] + ], + cidr: 'forbidden' }) }, ipv6Array: function(){ @@ -41,6 +43,64 @@ async function exec( cmd ){ } +function setRateLimitHeader( ctx, rateLimitInfo ){ + ctx.response.set({ + "X-RateLimit-Limit": rateLimitOpts.points, + "X-RateLimit-Remaining": rateLimitInfo.remainingPoints, + "X-RateLimit-Reset": new Date(Date.now() + rateLimitInfo.msBeforeNext).toUTCString() + }); +} + + +async function rateLimiterMiddleWare( ctx, next ){ + try { + let rlInfo = await rateLimiter.consume(ctx.ip) + setRateLimitHeader( ctx, rlInfo ); + await next().catch(); + } catch ( rlInfo ) { + ctx.status = 429 + ctx.response.set({ + "Retry-After": rlInfo.msBeforeNext / 1000, + }); + setRateLimitHeader( ctx, rlInfo ); + ctx.body = { error: 'Too Many Requests. Please wait for some time' }; + } +} + + +async function getIpList( ctx ){ + const ips = await exec(`ip -6 a show dev ${ethDev} scope global | grep inet6 | awk '{print $2}'`) + ctx.body = { ips : ips.split('\n').filter(v => v.length ).map( v => v.slice(0, -3)) }; +} + + +async function addIp( ctx ){ + const ip = ctx.request.body.ip; + try { + await exec(`sudo ip -6 a add ${ip}/64 dev ${ethDev}`); + } catch (e) { + ctx.status = 409; + ctx.body = { error: 'Failed to assign ip'}; + return; + } + + // Remove ip after some time + setTimeout(function(){ + exec(`sudo ip -6 a del dev ${ethDev} ${ip}/64`); + },60*1000 ); + + const ips = await exec(`ip -6 a show dev ${ethDev}| grep inet6.*global | awk '{print $2}'`); + ctx.body = { ips : ips.split('\n').filter(v => v.length ).map( v => v.slice(0, -3)) }; +} + + +async function logger( ctx, next ){ + const startTime = Date.now(); + await next(); + const requestTime = Date.now() - startTime; + console.log(`${ctx.method} ${ctx.response.status} ${ctx.ip} "${ctx.request.headers['user-agent']}" - ${requestTime}`); +} + router.route({ method: 'get', @@ -54,10 +114,7 @@ router.route({ } } }, - handler: async (ctx) => { - const ips = await exec(`ip -6 a show dev ${ethDev} scope global | grep inet6 | awk '{print $2}'`) - ctx.body = { ips : ips.split('\n').filter(v => v.length ).map( v => v.slice(0, -3)) }; - } + handler: getIpList }); @@ -77,43 +134,13 @@ router.route({ } } }, - handler: async (ctx) => { - const ip = ctx.request.body.ip; - try { - await exec(`sudo ip -6 a add ${ip}/64 dev ${ethDev}`); - } catch (e) { - ctx.status = 409; - ctx.body = { error: 'Failed to assign ip'}; - return; - } - - // Remove ip after some time - setTimeout(function(){ - exec(`sudo ip -6 a del dev ${ethDev} ${ip}/64`); - },60*1000 ); - - const ips = await exec(`ip -6 a show dev ${ethDev}| grep inet6.*global | awk '{print $2}'`); - ctx.body = { ips : ips.split('\n').filter(v => v.length ).map( v => v.slice(0, -3)) }; - } + handler: addIp }); -app.use(require('koa-static')('./static')); -app.use( async ( ctx, next ) => { - try { - await rateLimiter.consume(ctx.ip) - await next(); - } catch (rejRes) { - ctx.status = 429 - ctx.response.set({ - "Retry-After": rejRes.msBeforeNext / 1000, - "X-RateLimit-Limit": rateLimitOpts.points, - "X-RateLimit-Remaining": rejRes.remainingPoints, - "X-RateLimit-Reset": new Date(Date.now() + rejRes.msBeforeNext) - }); - ctx.body = { error: 'Too Many Requests. Please wait for some time' }; - } -}); -app.use(router.middleware()); +app.use( logger ); +app.use( require('koa-static')('./static') ); +app.use( rateLimiterMiddleWare ); +app.use( router.middleware() ); app.listen( 3553 ); diff --git a/static/app.js b/static/app.js index d827114..426e600 100644 --- a/static/app.js +++ b/static/app.js @@ -46,8 +46,7 @@ var app = new Vue({ this.ips = res.ips; }) .catch( res =>{ - console.log( res ); - // alert( res.error ) + alert( res.error ) }); } } diff --git a/static/index.html b/static/index.html index de51e00..c41a375 100644 --- a/static/index.html +++ b/static/index.html @@ -5,11 +5,19 @@
+ * List of public IPv6 IP assigned to the currently running server is shown below. + * We can click on each link and verify whether that IP is globally accessible or not by visiting the link + * Optionally, we can add new public IPv6 address using the form displayed in the bottom. + * Newly added IP will only persist for one minute. It will be removed after that. + * Please note that, we are only allowed change last 64 bits of existing global ip. Otherwise it won't work +