Refactor and split out things into cmd and lib (#77)

* Refactor anubis to split business logic into a lib, and cmd to just be direct usage.

* Post-rebase fixes.

* Update changelog, remove unnecessary one.

* lib: refactor this

This is mostly based on my personal preferences for how Go code should
be laid out. I'm not sold on the package name "lib" (I'd call it anubis
but that would stutter), but people are probably gonna import it as
libanubis so it's likely fine.

Packages have been "flattened" to centralize implementation with area of
concern. This goes against the Java-esque style that many people like,
but I think this helps make things simple.

Most notably: the dnsbl client (which is a hack) is an internal package
until it's made more generic. Then it can be made external.

I also fixed the logic such that `go generate` works and rebased on
main.

* internal/test: run tests iff npx exists and DONT_USE_NETWORK is not set

Signed-off-by: Xe Iaso <me@xeiaso.net>

* internal/test: install deps

Signed-off-by: Xe Iaso <me@xeiaso.net>

* .github/workflows: verbose go tests?

Signed-off-by: Xe Iaso <me@xeiaso.net>

* internal/test: sleep 2

Signed-off-by: Xe Iaso <me@xeiaso.net>

* internal/test: nix this test so CI works

Signed-off-by: Xe Iaso <me@xeiaso.net>

* internal/test: warmup per browser?

Signed-off-by: Xe Iaso <me@xeiaso.net>

* internal/test: disable for now :(

Signed-off-by: Xe Iaso <me@xeiaso.net>

* lib/anubis: do not apply bot rules if address check fails

Closes #83

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Yulian Kuncheff 2025-03-22 23:44:49 +01:00 committed by GitHub
parent af6f05554f
commit 6156d3d729
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 1116 additions and 818 deletions

95
internal/dnsbl/dnsbl.go Normal file
View file

@ -0,0 +1,95 @@
package dnsbl
import (
"errors"
"fmt"
"net"
"strings"
)
//go:generate go tool golang.org/x/tools/cmd/stringer -type=DroneBLResponse
type DroneBLResponse byte
const (
AllGood DroneBLResponse = 0
IRCDrone DroneBLResponse = 3
Bottler DroneBLResponse = 5
UnknownSpambotOrDrone DroneBLResponse = 6
DDOSDrone DroneBLResponse = 7
SOCKSProxy DroneBLResponse = 8
HTTPProxy DroneBLResponse = 9
ProxyChain DroneBLResponse = 10
OpenProxy DroneBLResponse = 11
OpenDNSResolver DroneBLResponse = 12
BruteForceAttackers DroneBLResponse = 13
OpenWingateProxy DroneBLResponse = 14
CompromisedRouter DroneBLResponse = 15
AutoRootingWorms DroneBLResponse = 16
AutoDetectedBotIP DroneBLResponse = 17
Unknown DroneBLResponse = 255
)
func Reverse(ip net.IP) string {
if ip.To4() != nil {
return reverse4(ip)
}
return reverse6(ip)
}
func reverse4(ip net.IP) string {
splitAddress := strings.Split(ip.String(), ".")
// swap first and last octet
splitAddress[0], splitAddress[3] = splitAddress[3], splitAddress[0]
// swap middle octets
splitAddress[1], splitAddress[2] = splitAddress[2], splitAddress[1]
return strings.Join(splitAddress, ".")
}
func reverse6(ip net.IP) string {
ipBytes := []byte(ip)
var sb strings.Builder
for i := len(ipBytes) - 1; i >= 0; i-- {
// Split the byte into two nibbles
highNibble := ipBytes[i] >> 4
lowNibble := ipBytes[i] & 0x0F
// Append the nibbles in reversed order
sb.WriteString(fmt.Sprintf("%x.%x.", lowNibble, highNibble))
}
return sb.String()[:len(sb.String())-1]
}
func Lookup(ipStr string) (DroneBLResponse, error) {
ip := net.ParseIP(ipStr)
if ip == nil {
return Unknown, errors.New("dnsbl: input is not an IP address")
}
revIP := Reverse(ip) + ".dnsbl.dronebl.org"
ips, err := net.LookupIP(revIP)
if err != nil {
var dnserr *net.DNSError
if errors.As(err, &dnserr) {
if dnserr.IsNotFound {
return AllGood, nil
}
}
return Unknown, err
}
if len(ips) != 0 {
for _, ip := range ips {
return DroneBLResponse(ip.To4()[3]), nil
}
}
return UnknownSpambotOrDrone, nil
}

View file

@ -0,0 +1,61 @@
package dnsbl
import (
"fmt"
"net"
"os"
"testing"
)
func TestReverse4(t *testing.T) {
cases := []struct {
inp, out string
}{
{"1.2.3.4", "4.3.2.1"},
}
for _, cs := range cases {
t.Run(fmt.Sprintf("%s->%s", cs.inp, cs.out), func(t *testing.T) {
out := reverse4(net.ParseIP(cs.inp))
if out != cs.out {
t.Errorf("wanted %s\ngot: %s", cs.out, out)
}
})
}
}
func TestReverse6(t *testing.T) {
cases := []struct {
inp, out string
}{
{
inp: "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0",
out: "0.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1",
},
}
for _, cs := range cases {
t.Run(fmt.Sprintf("%s->%s", cs.inp, cs.out), func(t *testing.T) {
out := reverse6(net.ParseIP(cs.inp))
if out != cs.out {
t.Errorf("wanted %s, got: %s", cs.out, out)
}
})
}
}
func TestLookup(t *testing.T) {
if os.Getenv("DONT_USE_NETWORK") != "" {
t.Skip("test requires network egress")
return
}
resp, err := Lookup("27.65.243.194")
if err != nil {
t.Fatalf("it broked: %v", err)
}
t.Logf("response: %d", resp)
}

View file

@ -0,0 +1,54 @@
// Code generated by "stringer -type=DroneBLResponse"; DO NOT EDIT.
package dnsbl
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[AllGood-0]
_ = x[IRCDrone-3]
_ = x[Bottler-5]
_ = x[UnknownSpambotOrDrone-6]
_ = x[DDOSDrone-7]
_ = x[SOCKSProxy-8]
_ = x[HTTPProxy-9]
_ = x[ProxyChain-10]
_ = x[OpenProxy-11]
_ = x[OpenDNSResolver-12]
_ = x[BruteForceAttackers-13]
_ = x[OpenWingateProxy-14]
_ = x[CompromisedRouter-15]
_ = x[AutoRootingWorms-16]
_ = x[AutoDetectedBotIP-17]
_ = x[Unknown-255]
}
const (
_DroneBLResponse_name_0 = "AllGood"
_DroneBLResponse_name_1 = "IRCDrone"
_DroneBLResponse_name_2 = "BottlerUnknownSpambotOrDroneDDOSDroneSOCKSProxyHTTPProxyProxyChainOpenProxyOpenDNSResolverBruteForceAttackersOpenWingateProxyCompromisedRouterAutoRootingWormsAutoDetectedBotIP"
_DroneBLResponse_name_3 = "Unknown"
)
var (
_DroneBLResponse_index_2 = [...]uint8{0, 7, 28, 37, 47, 56, 66, 75, 90, 109, 125, 142, 158, 175}
)
func (i DroneBLResponse) String() string {
switch {
case i == 0:
return _DroneBLResponse_name_0
case i == 3:
return _DroneBLResponse_name_1
case 5 <= i && i <= 17:
i -= 5
return _DroneBLResponse_name_2[_DroneBLResponse_index_2[i]:_DroneBLResponse_index_2[i+1]]
case i == 255:
return _DroneBLResponse_name_3
default:
return "DroneBLResponse(" + strconv.FormatInt(int64(i), 10) + ")"
}
}