Coder Social home page Coder Social logo

jxsl13 / goripr Goto Github PK

View Code? Open in Web Editor NEW
5.0 2.0 2.0 157 KB

Goripr is an acronym for Go Redis IP Ranges and allows a memory efficient mapping of IP ranges to strings. My need for this package comes from mapping VPN ranges to specific ban reasons in a memory efficient way.

License: MIT License

Go 99.35% Makefile 0.65%
redis vpn ip range ip-ranges go golang ipv4

goripr's Introduction

Go Redis IP Ranges (goripr)

Test Go Report Card GoDoc License: MIT codecov Sourcegraph deepsource

goripr is an eficient way to store IP ranges in a redis database and mapping those ranges to specific strings.

This package wraps the widely used redis Go client and extends its feature set with a storage efficient mapping of IPv4 ranges to specific strings called reasons.

I intend to use this package in my VPN Detection, that's why the term "reason" is used. The term refers to a ban reason that is given when a player using a VPN (they usually do that with malicious intent) gets banned. The string can be used in any other way needed, especially containing JSON formatted data.

Idea

The general approach is to save the beginning and the end of a range into the database. The beginning boundary has the property called LowerBound set to true and the last IP in a given range is called an upper boundary with the property UpperBound set to true. Based on these properties it is possible to determine, how to cut existing boundaries, when new IP ranges are inserted into the database.

Problem it solves

The VPN detection and especially the ban server used to save all IPs from the given ranges with their corresponding reasons into the database. That is the trivial approach, but proved to be inefficient when having more than 100 million individual IPs stored in the Redis database. At it's peak the database needed ~7GB of RAM, which is not a feasible solution, especially when the source files containing the actual ranges in their respective masked shorthand notation (x.x.x.x/24) needed less than one MB of storage space.

Gains over the trivial approach

On the other hand, iterating over ~50k such range strings was also not a feasible solution, especially when the ban server should react within ~1 second. The compromise should be a slower reaction time compared to the triavial approach, but way less of a RAM overhead. I guess that the reduction of RAM usage by a factor of about 240x should also improve the response time significantly, as the ~7GB approach was burdening even high performance servers rather heavily. The current RAM that is being used is about 30MB, which is acceptable.

Input format of the package

# custom IP range
84.141.32.1 - 84.141.32.255

# single IP
84.141.32.1

# subnet mask
84.141.32.1/24

Example

package main

import (
	"bufio"
	"context"
	"errors"
	"flag"
	"fmt"
	"os"
	"regexp"

	"github.com/jxsl13/goripr/v2"
)

var (
	splitRegex    = regexp.MustCompile(`([0-9.\-\s/]+)#?\s*(.*)\s*$`)
	defaultReason = "VPN - https://website.com"

	addFile = ""
	findIP  = ""
)

func init() {
	flag.StringVar(&addFile, "add", "", "-add filename.txt")
	flag.StringVar(&findIP, "find", "", "-find 123.0.0.1")
	flag.Parse()

	if addFile == "" && findIP == "" {
		flag.PrintDefaults()
		os.Exit(1)
	}
}

func parseLine(line string) (ip, reason string, err error) {
	if matches := splitRegex.FindStringSubmatch(line); len(matches) > 0 {
		return matches[1], matches[2], nil
	}
	return "", "", errors.New("empty")
}

func addIPsToDatabase(rdb *goripr.Client, ctx context.Context, filename string) error {
	file, err := os.Open(filename)
	if err != nil {
		return err
	}

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		ip, reason, err := parseLine(scanner.Text())
		if err != nil {
			continue
		}
		if reason == "" {
			reason = defaultReason
		}

		err = rdb.Insert(ctx, ip, reason)
		if err != nil {
			if !errors.Is(err, goripr.ErrInvalidRange) {
				fmt.Println(err, "Input:", ip)
			}
			continue
		}
	}
	return nil
}

func main() {
	ctx := context.Background()
	rdb, err := goripr.NewClient(ctx, goripr.Options{
		Addr: "localhost:6379",
		DB:   0,
	})
	if err != nil {
		fmt.Println("error:", err)
		os.Exit(1)
	}
	defer rdb.Close()

	if addFile != "" {
		err := addIPsToDatabase(rdb, ctx, addFile)
		if err != nil {
			fmt.Println("error:", err)
			os.Exit(1)
		}
	} else if findIP != "" {
		reason, err := rdb.Find(ctx, findIP)
		if err != nil {
			fmt.Println("IP:", findIP, "error:", err)
			os.Exit(1)
		}
		fmt.Println("IP:", findIP, "Reason:", reason)
		return
	}
}

// Output: IP: 84.141.32.1 Reason: any range where the first IP is smaller than the second
// Output: IP: 84.141.32.0 error: the given IP was not found in any database ranges

Example text file

84.141.32.1 - 84.141.32.255 # any range where the first IP is smaller than the second

2.56.92.0/22 # VPN subnet masking

# without a reason (uses default reason)
2.56.140.0/24

TODO

  • Optional Cache of requested IPs for like 24 hours in order to improve response time for recurring requests (rejoining players)

goripr's People

Contributors

deepsourcebot avatar dependabot[bot] avatar jxsl13 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

goripr's Issues

caching

It must be possible to remove all cached keys.

Question is, do we want to handle whitelists via caching or via the ip ranges goripr.Remove function?

first idea is to handle the caching via a c_t prefix where c stands for cache and t stands for temporary.

redis does only support the expiration of root level keys (without using lua scripting) forcing the cache to live in the same namespace as the ip range attributes.

[under review]Some logic error

2021/01/30 21:35:36 [NO VPN]: xxxxxxxx
2021/01/30 21:38:32 Subscriber pushed message into channel
panic: runtime error: index out of range [0] with length 0

goroutine 50 [running]:
github.com/jxsl13/goripr.(*Client).Find(0xc0000f8960, 0xc0003281a0, 0xd, 0x0, 0x0, 0x0, 0x0)
	/go/pkg/mod/github.com/jxsl13/[email protected]/redis.go:721 +0x5f8
main.processMessage(0xc0000162e0, 0x11, 0xc0000ea0b0, 0xaf, 0xc0000f8960, 0xc00004d680, 0x99bb00, 0x0, 0x0)
	/build/process_message.go:22 +0x31a
main.main.func1()
	/build/main.go:76 +0xe8
created by main.main
	/build/main.go:74 +0xa5

goripr/redis.go

Line 721 in 6d3f55d

belowNearest := below[0]

inconsistency panic

panic: reasons inconsistent

goroutine 114 [running]:
github.com/jxsl13/goripr/v2.(*Client).Find(0xc0005a9140, {0xa4e9a8, 0xc0003cd540}, {0xc000012e00?, 0xa4e7e8?})
        github.com/jxsl13/goripr/[email protected]/redis.go:670 +0x674
github.com/jxsl13/TeeworldsEconVPNDetectionGo/vpn.(*VPNChecker).foundInCache(0xc000054d34?, {0xc000012e00?, 0xc0000f4a5d?})
        github.com/jxsl13/TeeworldsEconVPNDetectionGo/vpn/checker.go:49 +0x36
github.com/jxsl13/TeeworldsEconVPNDetectionGo/vpn.(*VPNChecker).IsVPN(0xc0003ccd40, {0xc0000f4a5d?, 0x0?})
        github.com/jxsl13/TeeworldsEconVPNDetectionGo/vpn/checker.go:105 +0xf3
github.com/jxsl13/TeeworldsEconVPNDetectionGo/econ.vpnCheck(0xc000036120, {0xc0000f4a5d, 0xc}, 0xc000054fd0?, 0x4e94914f0000, {0xc00002c6a0, 0x1a})
        github.com/jxsl13/TeeworldsEconVPNDetectionGo/econ/econ.go:33 +0x55
created by github.com/jxsl13/TeeworldsEconVPNDetectionGo/econ.NewEvaluationRoutine in goroutine 65
        github.com/jxsl13/TeeworldsEconVPNDetectionGo/econ/econ.go:110 +0x711

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.