blocky icon indicating copy to clipboard operation
blocky copied to clipboard

Feature Request: support upstream routing using geosite.dat

Open luochen1990 opened this issue 8 months ago • 1 comments

https://github.com/v2fly/domain-list-community

It works like:

  1. The geosite.dat file path can be specified from configuration, like geodata_path: /path/to/geosite.dat
  2. The file will be loaded into RAM at runtime, and the protobuf encoded binary data will be decoded and prepared for matching.
  3. So we can reference it in the routing rules like geosite:private -> upstream1, which means the query that contained in geosite:private will be route to upstream1

Automatic updates will be better, but not the most important feature, update between restart is also acceptable.

luochen1990 avatar Apr 23 '25 12:04 luochen1990


import (
	"context"
	"net"
	"strings"
	"time"

	"github.com/0xERR0R/blocky/config"
	"github.com/0xERR0R/blocky/geosite"
	"github.com/0xERR0R/blocky/log"
	"github.com/miekg/dns"
)

type BlockingResolver struct {
	config         *config.Blocking
	geositeManager *geosite.GeositeManager
	// ...
}

func NewBlockingResolver(cfg *config.Config, geositeManager *geosite.GeositeManager) *BlockingResolver {
	return &BlockingResolver{
		config:         &cfg.Blocking,
		geositeManager: geositeManager,
		// ...
	}
}

func (r *BlockingResolver) Resolve(ctx context.Context, request *Request) (response *Response, err error) {
	logger := log.LoggerFromContext(ctx).WithFields(log.Fields{
		"client_ip":   request.ClientIP,
		"client_name": request.ClientNames,
		"question":    request.Req.Question[0].Name,
	})

	queryDomain := strings.TrimSuffix(request.Req.Question[0].Name, ".")

	// Check nxDomainDomains first
	for _, nxDomain := range r.config.NxDomainDomains {
		if strings.EqualFold(queryDomain, nxDomain) {
			logger.Info("returning NXDOMAIN for domain in nxDomainDomains")
			resp := new(dns.Msg)
			resp.SetRcode(request.Req, dns.RcodeNameError)
			return &Response{Res: resp, RType: BLOCKED, Reason: "NXDOMAIN override"}, nil
		}
	}

	// Check blacklists (including geosite: rules)
	clientNames := r.clientLookup.GetClientNames(request.ClientIP)
	blockGroups := r.getBlockGroups(clientNames)
	isBlocked := false
	var reason string

	for _, group := range blockGroups {
		for _, list := range r.config.BlackLists[group] {
			if strings.HasPrefix(list, "geosite:") {
				category := strings.TrimPrefix(list, "geosite:")
				if r.geositeManager.Match(queryDomain, category) {
					isBlocked = true
					reason = fmt.Sprintf("Blocked by geosite:%s", category)
					break
				}
			} else if r.isBlocked(queryDomain, list) {
				isBlocked = true
				reason = fmt.Sprintf("Blocked by group: %s", group)
				break
			}
		}
		if isBlocked {
			break
		}
	}

	if !isBlocked {
		return r.next.Resolve(ctx, request)
	}

	// Apply blockType
	resp := new(dns.Msg)
	if r.config.BlockType == "nxDomain" {
		resp.SetRcode(request.Req, dns.RcodeNameError)
	} else {
		resp.SetReply(request.Req)
		if request.Req.Question[0].Qtype == dns.TypeA {
			resp.Answer = []dns.RR{&dns.A{
				Hdr: dns.RR_Header{Name: request.Req.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: uint32(r.config.BlockTTL / time.Second)},
				A:   net.ParseIP("0.0.0.0"),
			}}
		} else if request.Req.Question[0].Qtype == dns.TypeAAAA {
			resp.Answer = []dns.RR{&dns.AAAA{
				Hdr:  dns.RR_Header{Name: request.Req.Question[0].Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: uint32(r.config.BlockTTL / time.Second)},
				AAAA: net.ParseIP("::"),
			}}
		}
	}

	return &Response{Res: resp, RType: BLOCKED, Reason: reason}, nil
}

ljluestc avatar May 21 '25 12:05 ljluestc

This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Aug 20 '25 04:08 github-actions[bot]

This issue was closed because it has been stalled for 5 days with no activity.

github-actions[bot] avatar Aug 25 '25 04:08 github-actions[bot]