diff --git a/irc/flatip/flatip.go b/irc/flatip/flatip.go index 7c603350..0084cf2b 100644 --- a/irc/flatip/flatip.go +++ b/irc/flatip/flatip.go @@ -155,6 +155,14 @@ func (cidr IPNet) Contains(ip IP) bool { return cidr.IP == maskedIP } +func (cidr IPNet) Size() (ones, bits int) { + if cidr.IP.IsIPv4() { + return int(cidr.PrefixLen) - 96, 32 + } else { + return int(cidr.PrefixLen), 128 + } +} + // FromNetIPnet converts a net.IPNet into an IPNet. func FromNetIPNet(network net.IPNet) (result IPNet) { ones, _ := network.Mask.Size() diff --git a/irc/flatip/flatip_test.go b/irc/flatip/flatip_test.go index c2aae9a8..f689d6dd 100644 --- a/irc/flatip/flatip_test.go +++ b/irc/flatip/flatip_test.go @@ -2,8 +2,10 @@ package flatip import ( "bytes" + "fmt" "math/rand" "net" + "reflect" "testing" "time" ) @@ -86,6 +88,38 @@ func doMaskingTest(ip net.IP, t *testing.T) { } } +func assertEqual(found, expected interface{}) { + if !reflect.DeepEqual(found, expected) { + panic(fmt.Sprintf("expected %#v, found %#v", expected, found)) + } +} + +func TestSize(t *testing.T) { + _, net, err := ParseCIDR("8.8.8.8/24") + if err != nil { + panic(err) + } + ones, bits := net.Size() + assertEqual(ones, 24) + assertEqual(bits, 32) + + _, net, err = ParseCIDR("2001::0db8/64") + if err != nil { + panic(err) + } + ones, bits = net.Size() + assertEqual(ones, 64) + assertEqual(bits, 128) + + _, net, err = ParseCIDR("2001::0db8/96") + if err != nil { + panic(err) + } + ones, bits = net.Size() + assertEqual(ones, 96) + assertEqual(bits, 128) +} + func TestMasking(t *testing.T) { for _, ipstr := range testIPStrs { doMaskingTest(easyParseIP(ipstr), t) diff --git a/irc/uban.go b/irc/uban.go index 424d1b7a..1b1c376f 100644 --- a/irc/uban.go +++ b/irc/uban.go @@ -366,7 +366,14 @@ func ubanInfoHandler(client *Client, target ubanTarget, params []string, rb *Res } func ubanInfoCIDR(client *Client, target ubanTarget, rb *ResponseBuffer) { - if target.cidr.PrefixLen == 128 { + config := client.server.Config() + // show connection limiter/throttler state if this CIDR is entirely + // contained in a single limiter/throttler bucket: + ones, bits := target.cidr.Size() + showLimiter := (bits == 32 && ones >= config.Server.IPLimits.CidrLenIPv4) || + (bits == 128 && ones >= config.Server.IPLimits.CidrLenIPv6) + sendMaskWarning := (bits == 128 && ones > config.Server.IPLimits.CidrLenIPv6) + if showLimiter { netName, status := client.server.connectionLimiter.Status(target.cidr.IP) if status.Exempt { rb.Notice(fmt.Sprintf(client.t("IP %s is exempt from connection limits"), target.cidr.IP.String())) @@ -391,6 +398,10 @@ func ubanInfoCIDR(client *Client, target ubanTarget, rb *ResponseBuffer) { rb.Notice(line) } } + if sendMaskWarning { + rb.Notice(fmt.Sprintf(client.t("Note: try evaluating a wider IPv6 CIDR like %s/%d"), + target.cidr.IP.String(), config.Server.IPLimits.CidrLenIPv6)) + } } func ubanInfoNickmask(client *Client, target ubanTarget, rb *ResponseBuffer) {