mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-05 11:06:23 -07:00
Improve performance on gcutil.ParseIPRange for IPv6
This commit is contained in:
parent
9029163c40
commit
80f2b98147
4 changed files with 232 additions and 75 deletions
86
pkg/gcutil/iprange.go
Normal file
86
pkg/gcutil/iprange.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package gcutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidIP = errors.New("invalid IP address")
|
||||
ErrInvalidSubnet = errors.New("invalid IP address or subnet mask")
|
||||
)
|
||||
|
||||
// ParseIPRange takes a single IP address or an IP range of the form "networkIP/netmaskbits" and
|
||||
// gives the starting IP and ending IP in the subnet
|
||||
//
|
||||
// More info: https://en.wikipedia.org/wiki/Subnet
|
||||
func ParseIPRange(ipOrCIDR string) (string, string, error) {
|
||||
parts := strings.Split(ipOrCIDR, "/")
|
||||
ip := net.ParseIP(parts[0])
|
||||
if ip == nil {
|
||||
return "", "", ErrInvalidIP
|
||||
}
|
||||
ipv4 := ip.To4()
|
||||
if ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
// single IP
|
||||
return ipOrCIDR, ipOrCIDR, nil
|
||||
} else if len(parts) == 2 {
|
||||
// IP/mask
|
||||
netBits, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
mask := net.CIDRMask(netBits, len(ip)*8)
|
||||
broadcast := net.IP(make([]byte, len(ip)))
|
||||
for i := range ip {
|
||||
broadcast[i] = ip[i] | ^mask[i]
|
||||
}
|
||||
return ip.Mask(mask).String(), broadcast.String(), nil
|
||||
}
|
||||
return "", "", ErrInvalidSubnet
|
||||
}
|
||||
|
||||
// GetIPRangeSubnet returns the smallest subnet that contains the start and end
|
||||
// IP addresses, and any errors that occured
|
||||
func GetIPRangeSubnet(start string, end string) (*net.IPNet, error) {
|
||||
startIP := net.ParseIP(start)
|
||||
endIP := net.ParseIP(end)
|
||||
if startIP == nil {
|
||||
return nil, fmt.Errorf("invalid IP address %s", start)
|
||||
}
|
||||
if endIP == nil {
|
||||
return nil, fmt.Errorf("invalid IP address %s", end)
|
||||
}
|
||||
if len(startIP) != len(endIP) {
|
||||
return nil, errors.New("ip addresses must both be IPv4 or IPv6")
|
||||
}
|
||||
|
||||
if startIP.To4() != nil {
|
||||
startIP = startIP.To4()
|
||||
endIP = endIP.To4()
|
||||
}
|
||||
|
||||
bits := 0
|
||||
var ipn *net.IPNet
|
||||
for b := range startIP {
|
||||
if startIP[b] == endIP[b] {
|
||||
bits += 8
|
||||
continue
|
||||
}
|
||||
for i := 7; i >= 0; i-- {
|
||||
if startIP[b]&(1<<i) == endIP[b]&(1<<i) {
|
||||
bits++
|
||||
continue
|
||||
}
|
||||
ipn = &net.IPNet{IP: startIP, Mask: net.CIDRMask(bits, len(startIP)*8)}
|
||||
return ipn, nil
|
||||
}
|
||||
}
|
||||
return &net.IPNet{IP: startIP, Mask: net.CIDRMask(bits, len(startIP)*8)}, nil
|
||||
}
|
145
pkg/gcutil/iprange_test.go
Normal file
145
pkg/gcutil/iprange_test.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package gcutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIPRangeErrOnInvalidIP(t *testing.T) {
|
||||
_, _, err := ParseIPRange("not an IP")
|
||||
assert.Error(t, err)
|
||||
_, _, err = ParseIPRange("")
|
||||
assert.Error(t, err)
|
||||
_, _, err = ParseIPRange("192.168.56.0/")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIPRangeSingleIP(t *testing.T) {
|
||||
start, end, err := ParseIPRange("192.168.56.1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, start, end)
|
||||
start, end, err = ParseIPRange("2801::")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, start, end)
|
||||
}
|
||||
|
||||
func TestIPRangeIPv4Range(t *testing.T) {
|
||||
ranges := []string{"192.168.56.0/24", "192.168.0.0/16", "192.0.0.0/8"}
|
||||
starts := []string{"192.168.56.0", "192.168.0.0", "192.0.0.0"}
|
||||
ends := []string{"192.168.56.255", "192.168.255.255", "192.255.255.255"}
|
||||
for i := range ranges {
|
||||
start, end, err := ParseIPRange(ranges[i])
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, starts[i], start)
|
||||
assert.Equal(t, ends[i], end)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPRangeIPv6Range(t *testing.T) {
|
||||
ranges := []string{
|
||||
"2607:f8b0:400a:80a::2010/124",
|
||||
"2607:f8b0:400a:80a::2000/120",
|
||||
"2607:f8b0:400a:80a::2000/116",
|
||||
"2607:f8b0:400a:80a::/112",
|
||||
"2607:f8b0:400a:80a::/108",
|
||||
"2607:f8b0:400a:80a::/104",
|
||||
"2607:f8b0:400a:80a::/100",
|
||||
"2607:f8b0:400a:80a::/96",
|
||||
"2607:f8b0:400a:80a::/92",
|
||||
"2607:f8b0:400a:80a::/88",
|
||||
"2607:f8b0:400a:80a::/84",
|
||||
"2607:f8b0:400a:80a::/80",
|
||||
"2607:f8b0:400a:80a::/76",
|
||||
"2607:f8b0:400a:80a::/72",
|
||||
"2607:f8b0:400a:80a::/68",
|
||||
"2607:f8b0:400a:80a::/64",
|
||||
"2607:f8b0:400a:80a::/60",
|
||||
"2607:f8b0:400a:80a::/56",
|
||||
"2607:f8b0:400a:80a::/52",
|
||||
"2607:f8b0:400a:80a::/48",
|
||||
"2607:f8b0:400a:80a::/44",
|
||||
"2607:f8b0:400a:80a::/40",
|
||||
"2607:f8b0:400a:80a::/36",
|
||||
"2607:f8b0:400a:80a::/32",
|
||||
"2607:f8b0:400a:80a::/28",
|
||||
"2607:f8b0:400a:80a::/24",
|
||||
"2607:f8b0:400a:80a::/20",
|
||||
"2607:f8b0:400a:80a::/16",
|
||||
"2607:f8b0:400a:80a::/12",
|
||||
"2607:f8b0:400a:80a::/8",
|
||||
"2607:f8b0:400a:80a::/4",
|
||||
}
|
||||
starts := []string{
|
||||
"2607:f8b0:400a:80a::2010",
|
||||
"2607:f8b0:400a:80a::2000",
|
||||
"2607:f8b0:400a:80a::2000",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:80a::",
|
||||
"2607:f8b0:400a:800::",
|
||||
"2607:f8b0:400a:800::",
|
||||
"2607:f8b0:400a::",
|
||||
"2607:f8b0:400a::",
|
||||
"2607:f8b0:4000::",
|
||||
"2607:f8b0:4000::",
|
||||
"2607:f8b0:4000::",
|
||||
"2607:f8b0::",
|
||||
"2607:f8b0::",
|
||||
"2607:f800::",
|
||||
"2607:f000::",
|
||||
"2607::",
|
||||
"2600::",
|
||||
"2600::",
|
||||
"2000::",
|
||||
}
|
||||
ends := []string{
|
||||
"2607:f8b0:400a:80a::201f",
|
||||
"2607:f8b0:400a:80a::20ff",
|
||||
"2607:f8b0:400a:80a::2fff",
|
||||
"2607:f8b0:400a:80a::ffff",
|
||||
"2607:f8b0:400a:80a::f:ffff",
|
||||
"2607:f8b0:400a:80a::ff:ffff",
|
||||
"2607:f8b0:400a:80a::fff:ffff",
|
||||
"2607:f8b0:400a:80a::ffff:ffff",
|
||||
"2607:f8b0:400a:80a:0:f:ffff:ffff",
|
||||
"2607:f8b0:400a:80a:0:ff:ffff:ffff",
|
||||
"2607:f8b0:400a:80a:0:fff:ffff:ffff",
|
||||
"2607:f8b0:400a:80a:0:ffff:ffff:ffff",
|
||||
"2607:f8b0:400a:80a:f:ffff:ffff:ffff",
|
||||
"2607:f8b0:400a:80a:ff:ffff:ffff:ffff",
|
||||
"2607:f8b0:400a:80a:fff:ffff:ffff:ffff",
|
||||
"2607:f8b0:400a:80a:ffff:ffff:ffff:ffff",
|
||||
"2607:f8b0:400a:80f:ffff:ffff:ffff:ffff",
|
||||
"2607:f8b0:400a:8ff:ffff:ffff:ffff:ffff",
|
||||
"2607:f8b0:400a:fff:ffff:ffff:ffff:ffff",
|
||||
"2607:f8b0:400a:ffff:ffff:ffff:ffff:ffff",
|
||||
"2607:f8b0:400f:ffff:ffff:ffff:ffff:ffff",
|
||||
"2607:f8b0:40ff:ffff:ffff:ffff:ffff:ffff",
|
||||
"2607:f8b0:4fff:ffff:ffff:ffff:ffff:ffff",
|
||||
"2607:f8b0:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"2607:f8bf:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"2607:f8ff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"2607:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"2607:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"260f:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"26ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"2fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
}
|
||||
for i := range ranges {
|
||||
start, end, err := ParseIPRange(ranges[i])
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, starts[i], start, "unequal values at index %d", i)
|
||||
assert.Equal(t, ends[i], end, "unequal values at index %d", i)
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import (
|
|||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
@ -161,79 +160,6 @@ func MarshalJSON(data interface{}, indent bool) (string, error) {
|
|||
return string(jsonBytes), err
|
||||
}
|
||||
|
||||
// ParseIPRange takes a single IP address or an IP range of the form "networkIP/netmaskbits" and
|
||||
// gives the starting IP and ending IP in the subnet
|
||||
//
|
||||
// More info: https://en.wikipedia.org/wiki/Subnet
|
||||
func ParseIPRange(ipOrCIDR string) (string, string, error) {
|
||||
var ipStart netip.Addr
|
||||
|
||||
if strings.ContainsRune(ipOrCIDR, '/') {
|
||||
var ipEnd netip.Addr
|
||||
// CIDR range
|
||||
prefix, err := netip.ParsePrefix(ipOrCIDR)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
ipStart = prefix.Addr()
|
||||
ipEnd = prefix.Addr()
|
||||
var tmp netip.Addr
|
||||
for {
|
||||
tmp = ipEnd.Next()
|
||||
if !prefix.Contains(tmp) {
|
||||
break
|
||||
}
|
||||
ipEnd = tmp
|
||||
}
|
||||
return ipStart.String(), ipEnd.String(), nil
|
||||
}
|
||||
// single IP
|
||||
var err error
|
||||
if ipStart, err = netip.ParseAddr(ipOrCIDR); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return ipStart.String(), ipStart.String(), nil
|
||||
}
|
||||
|
||||
// GetIPRangeSubnet returns the smallest subnet that contains the start and end
|
||||
// IP addresses, and any errors that occured
|
||||
func GetIPRangeSubnet(start string, end string) (*net.IPNet, error) {
|
||||
startIP := net.ParseIP(start)
|
||||
endIP := net.ParseIP(end)
|
||||
if startIP == nil {
|
||||
return nil, fmt.Errorf("invalid IP address %s", start)
|
||||
}
|
||||
if endIP == nil {
|
||||
return nil, fmt.Errorf("invalid IP address %s", end)
|
||||
}
|
||||
if len(startIP) != len(endIP) {
|
||||
return nil, errors.New("ip addresses must both be IPv4 or IPv6")
|
||||
}
|
||||
|
||||
if startIP.To4() != nil {
|
||||
startIP = startIP.To4()
|
||||
endIP = endIP.To4()
|
||||
}
|
||||
|
||||
bits := 0
|
||||
var ipn *net.IPNet
|
||||
for b := range startIP {
|
||||
if startIP[b] == endIP[b] {
|
||||
bits += 8
|
||||
continue
|
||||
}
|
||||
for i := 7; i >= 0; i-- {
|
||||
if startIP[b]&(1<<i) == endIP[b]&(1<<i) {
|
||||
bits++
|
||||
continue
|
||||
}
|
||||
ipn = &net.IPNet{IP: startIP, Mask: net.CIDRMask(bits, len(startIP)*8)}
|
||||
return ipn, nil
|
||||
}
|
||||
}
|
||||
return &net.IPNet{IP: startIP, Mask: net.CIDRMask(bits, len(startIP)*8)}, nil
|
||||
}
|
||||
|
||||
// ParseName takes a name string from a request object and returns the name and tripcode parts
|
||||
func ParseName(name string) (string, string) {
|
||||
var namePart string
|
||||
|
|
|
@ -13,7 +13,7 @@ var (
|
|||
geoipHandlers = make(map[string]GeoIPHandler)
|
||||
activeHandler GeoIPHandler
|
||||
|
||||
ErrInvalidIP = errors.New("invalid IP address")
|
||||
ErrInvalidIP = gcutil.ErrInvalidIP
|
||||
ErrNotConfigured = errors.New("geoip is not configured")
|
||||
ErrUnrecognized = errors.New("unrecognized GeoIP handler ID")
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue