123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- //go:build linux
- package resolved
- import (
- "context"
- "errors"
- "fmt"
- "net/netip"
- "os"
- "os/user"
- "path/filepath"
- "strconv"
- "strings"
- "syscall"
- "github.com/sagernet/sing-box/adapter"
- C "github.com/sagernet/sing-box/constant"
- "github.com/sagernet/sing-box/dns"
- "github.com/sagernet/sing-box/log"
- "github.com/sagernet/sing/common"
- E "github.com/sagernet/sing/common/exceptions"
- F "github.com/sagernet/sing/common/format"
- M "github.com/sagernet/sing/common/metadata"
- "github.com/godbus/dbus/v5"
- mDNS "github.com/miekg/dns"
- )
- type resolve1Manager Service
- type Address struct {
- IfIndex int32
- Family int32
- Address []byte
- }
- type Name struct {
- IfIndex int32
- Hostname string
- }
- type ResourceRecord struct {
- IfIndex int32
- Type uint16
- Class uint16
- Data []byte
- }
- type SRVRecord struct {
- Priority uint16
- Weight uint16
- Port uint16
- Hostname string
- Addresses []Address
- CNAME string
- }
- type TXTRecord []byte
- type LinkDNS struct {
- Family int32
- Address []byte
- }
- type LinkDNSEx struct {
- Family int32
- Address []byte
- Port uint16
- Name string
- }
- type LinkDomain struct {
- Domain string
- RoutingOnly bool
- }
- func (t *resolve1Manager) getLink(ifIndex int32) (*TransportLink, *dbus.Error) {
- link, loaded := t.links[ifIndex]
- if !loaded {
- link = &TransportLink{}
- t.links[ifIndex] = link
- iif, err := t.network.InterfaceFinder().ByIndex(int(ifIndex))
- if err != nil {
- return nil, wrapError(err)
- }
- link.iif = iif
- }
- return link, nil
- }
- func (t *resolve1Manager) getSenderProcess(sender dbus.Sender) (int32, error) {
- var senderPid int32
- dbusObject := t.systemBus.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
- if dbusObject == nil {
- return 0, E.New("missing dbus object")
- }
- err := dbusObject.Call("org.freedesktop.DBus.GetConnectionUnixProcessID", 0, string(sender)).Store(&senderPid)
- if err != nil {
- return 0, E.Cause(err, "GetConnectionUnixProcessID")
- }
- return senderPid, nil
- }
- func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundContext {
- var metadata adapter.InboundContext
- metadata.Inbound = t.Tag()
- metadata.InboundType = C.TypeResolved
- senderPid, err := t.getSenderProcess(sender)
- if err != nil {
- return metadata
- }
- var processInfo adapter.ConnectionOwner
- metadata.ProcessInfo = &processInfo
- processInfo.ProcessID = uint32(senderPid)
- processPath, err := os.Readlink(F.ToString("/proc/", senderPid, "/exe"))
- if err == nil {
- processInfo.ProcessPath = processPath
- } else {
- processPath, err = os.Readlink(F.ToString("/proc/", senderPid, "/comm"))
- if err == nil {
- processInfo.ProcessPath = processPath
- }
- }
- var uidFound bool
- statusContent, err := os.ReadFile(F.ToString("/proc/", senderPid, "/status"))
- if err == nil {
- for _, line := range strings.Split(string(statusContent), "\n") {
- line = strings.TrimSpace(line)
- if strings.HasPrefix(line, "Uid:") {
- fields := strings.Fields(line)
- if len(fields) >= 2 {
- uid, parseErr := strconv.ParseUint(fields[1], 10, 32)
- if parseErr != nil {
- break
- }
- processInfo.UserId = int32(uid)
- uidFound = true
- if osUser, _ := user.LookupId(F.ToString(uid)); osUser != nil {
- processInfo.UserName = osUser.Username
- }
- break
- }
- }
- }
- }
- if !uidFound {
- metadata.ProcessInfo.UserId = -1
- }
- return metadata
- }
- func (t *resolve1Manager) log(sender dbus.Sender, message ...any) {
- metadata := t.createMetadata(sender)
- if metadata.ProcessInfo != nil {
- var prefix string
- if metadata.ProcessInfo.ProcessPath != "" {
- prefix = filepath.Base(metadata.ProcessInfo.ProcessPath)
- } else if metadata.ProcessInfo.UserName != "" {
- prefix = F.ToString("user:", metadata.ProcessInfo.UserName)
- } else if metadata.ProcessInfo.UserId != 0 {
- prefix = F.ToString("uid:", metadata.ProcessInfo.UserId)
- }
- t.logger.Info("(", prefix, ") ", F.ToString(message...))
- } else {
- t.logger.Info(F.ToString(message...))
- }
- }
- func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context.Context {
- ctx := log.ContextWithNewID(t.ctx)
- metadata := t.createMetadata(sender)
- if metadata.ProcessInfo != nil {
- var prefix string
- if metadata.ProcessInfo.ProcessPath != "" {
- prefix = filepath.Base(metadata.ProcessInfo.ProcessPath)
- } else if metadata.ProcessInfo.UserName != "" {
- prefix = F.ToString("user:", metadata.ProcessInfo.UserName)
- } else if metadata.ProcessInfo.UserId != 0 {
- prefix = F.ToString("uid:", metadata.ProcessInfo.UserId)
- }
- t.logger.InfoContext(ctx, "(", prefix, ") ", strings.Join(F.MapToString(message), " "))
- } else {
- t.logger.InfoContext(ctx, strings.Join(F.MapToString(message), " "))
- }
- return adapter.WithContext(ctx, &metadata)
- }
- func familyToString(family int32) string {
- switch family {
- case syscall.AF_UNSPEC:
- return "AF_UNSPEC"
- case syscall.AF_INET:
- return "AF_INET"
- case syscall.AF_INET6:
- return "AF_INET6"
- default:
- return F.ToString(family)
- }
- }
- func (t *resolve1Manager) ResolveHostname(sender dbus.Sender, ifIndex int32, hostname string, family int32, flags uint64) (addresses []Address, canonical string, outflags uint64, err *dbus.Error) {
- t.linkAccess.Lock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return
- }
- t.linkAccess.Unlock()
- var strategy C.DomainStrategy
- switch family {
- case syscall.AF_UNSPEC:
- strategy = C.DomainStrategyAsIS
- case syscall.AF_INET:
- strategy = C.DomainStrategyIPv4Only
- case syscall.AF_INET6:
- strategy = C.DomainStrategyIPv6Only
- }
- ctx := t.logRequest(sender, "ResolveHostname ", link.iif.Name, " ", hostname, " ", familyToString(family), " ", flags)
- responseAddresses, lookupErr := t.dnsRouter.Lookup(ctx, hostname, adapter.DNSQueryOptions{
- LookupStrategy: strategy,
- })
- if lookupErr != nil {
- err = wrapError(err)
- return
- }
- addresses = common.Map(responseAddresses, func(it netip.Addr) Address {
- var addrFamily int32
- if it.Is4() {
- addrFamily = syscall.AF_INET
- } else {
- addrFamily = syscall.AF_INET6
- }
- return Address{
- IfIndex: ifIndex,
- Family: addrFamily,
- Address: it.AsSlice(),
- }
- })
- canonical = mDNS.CanonicalName(hostname)
- return
- }
- func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, family int32, address []byte, flags uint64) (names []Name, outflags uint64, err *dbus.Error) {
- t.linkAccess.Lock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return
- }
- t.linkAccess.Unlock()
- addr, ok := netip.AddrFromSlice(address)
- if !ok {
- err = wrapError(E.New("invalid address"))
- return
- }
- var nibbles []string
- for i := len(address) - 1; i >= 0; i-- {
- b := address[i]
- nibbles = append(nibbles, fmt.Sprintf("%x", b&0x0F))
- nibbles = append(nibbles, fmt.Sprintf("%x", b>>4))
- }
- var ptrDomain string
- if addr.Is4() {
- ptrDomain = strings.Join(nibbles, ".") + ".in-addr.arpa."
- } else {
- ptrDomain = strings.Join(nibbles, ".") + ".ip6.arpa."
- }
- request := &mDNS.Msg{
- MsgHdr: mDNS.MsgHdr{
- RecursionDesired: true,
- },
- Question: []mDNS.Question{
- {
- Name: mDNS.Fqdn(ptrDomain),
- Qtype: mDNS.TypePTR,
- Qclass: mDNS.ClassINET,
- },
- },
- }
- ctx := t.logRequest(sender, "ResolveAddress ", link.iif.Name, familyToString(family), addr, flags)
- var metadata adapter.InboundContext
- metadata.InboundType = t.Type()
- metadata.Inbound = t.Tag()
- response, lookupErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})
- if lookupErr != nil {
- err = wrapError(err)
- return
- }
- if response.Rcode != mDNS.RcodeSuccess {
- err = rcodeError(response.Rcode)
- return
- }
- for _, rawRR := range response.Answer {
- switch rr := rawRR.(type) {
- case *mDNS.PTR:
- names = append(names, Name{
- IfIndex: ifIndex,
- Hostname: rr.Ptr,
- })
- }
- }
- return
- }
- func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) {
- t.linkAccess.Lock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return
- }
- t.linkAccess.Unlock()
- request := &mDNS.Msg{
- MsgHdr: mDNS.MsgHdr{
- RecursionDesired: true,
- },
- Question: []mDNS.Question{
- {
- Name: mDNS.Fqdn(hostname),
- Qtype: qType,
- Qclass: qClass,
- },
- },
- }
- ctx := t.logRequest(sender, "ResolveRecord", link.iif.Name, hostname, mDNS.Class(qClass), mDNS.Type(qType), flags)
- var metadata adapter.InboundContext
- metadata.InboundType = t.Type()
- metadata.Inbound = t.Tag()
- response, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})
- if exchangeErr != nil {
- err = wrapError(exchangeErr)
- return
- }
- if response.Rcode != mDNS.RcodeSuccess {
- err = rcodeError(response.Rcode)
- return
- }
- for _, rr := range response.Answer {
- var record ResourceRecord
- record.IfIndex = ifIndex
- record.Type = rr.Header().Rrtype
- record.Class = rr.Header().Class
- data := make([]byte, mDNS.Len(rr))
- _, unpackErr := mDNS.PackRR(rr, data, 0, nil, false)
- if unpackErr != nil {
- err = wrapError(unpackErr)
- }
- record.Data = data
- records = append(records, record)
- }
- return
- }
- func (t *resolve1Manager) ResolveService(sender dbus.Sender, ifIndex int32, hostname string, sType string, domain string, family int32, flags uint64) (srvData []SRVRecord, txtData []TXTRecord, canonicalName string, canonicalType string, canonicalDomain string, outflags uint64, err *dbus.Error) {
- t.linkAccess.Lock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return
- }
- t.linkAccess.Unlock()
- serviceName := hostname
- if hostname != "" && !strings.HasSuffix(hostname, ".") {
- serviceName += "."
- }
- serviceName += sType
- if !strings.HasSuffix(serviceName, ".") {
- serviceName += "."
- }
- serviceName += domain
- if !strings.HasSuffix(serviceName, ".") {
- serviceName += "."
- }
- ctx := t.logRequest(sender, "ResolveService ", link.iif.Name, " ", hostname, " ", sType, " ", domain, " ", familyToString(family), " ", flags)
- srvRequest := &mDNS.Msg{
- MsgHdr: mDNS.MsgHdr{
- RecursionDesired: true,
- },
- Question: []mDNS.Question{
- {
- Name: serviceName,
- Qtype: mDNS.TypeSRV,
- Qclass: mDNS.ClassINET,
- },
- },
- }
- var metadata adapter.InboundContext
- metadata.InboundType = t.Type()
- metadata.Inbound = t.Tag()
- srvResponse, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), srvRequest, adapter.DNSQueryOptions{})
- if exchangeErr != nil {
- err = wrapError(exchangeErr)
- return
- }
- if srvResponse.Rcode != mDNS.RcodeSuccess {
- err = rcodeError(srvResponse.Rcode)
- return
- }
- txtRequest := &mDNS.Msg{
- MsgHdr: mDNS.MsgHdr{
- RecursionDesired: true,
- },
- Question: []mDNS.Question{
- {
- Name: serviceName,
- Qtype: mDNS.TypeTXT,
- Qclass: mDNS.ClassINET,
- },
- },
- }
- txtResponse, exchangeErr := t.dnsRouter.Exchange(ctx, txtRequest, adapter.DNSQueryOptions{})
- if exchangeErr != nil {
- err = wrapError(exchangeErr)
- return
- }
- for _, rawRR := range srvResponse.Answer {
- switch rr := rawRR.(type) {
- case *mDNS.SRV:
- var srvRecord SRVRecord
- srvRecord.Priority = rr.Priority
- srvRecord.Weight = rr.Weight
- srvRecord.Port = rr.Port
- srvRecord.Hostname = rr.Target
- var strategy C.DomainStrategy
- switch family {
- case syscall.AF_UNSPEC:
- strategy = C.DomainStrategyAsIS
- case syscall.AF_INET:
- strategy = C.DomainStrategyIPv4Only
- case syscall.AF_INET6:
- strategy = C.DomainStrategyIPv6Only
- }
- addrs, lookupErr := t.dnsRouter.Lookup(ctx, rr.Target, adapter.DNSQueryOptions{
- LookupStrategy: strategy,
- })
- if lookupErr == nil {
- srvRecord.Addresses = common.Map(addrs, func(it netip.Addr) Address {
- var addrFamily int32
- if it.Is4() {
- addrFamily = syscall.AF_INET
- } else {
- addrFamily = syscall.AF_INET6
- }
- return Address{
- IfIndex: ifIndex,
- Family: addrFamily,
- Address: it.AsSlice(),
- }
- })
- }
- for _, a := range srvResponse.Answer {
- if cname, ok := a.(*mDNS.CNAME); ok && cname.Header().Name == rr.Target {
- srvRecord.CNAME = cname.Target
- break
- }
- }
- srvData = append(srvData, srvRecord)
- }
- }
- for _, rawRR := range txtResponse.Answer {
- switch rr := rawRR.(type) {
- case *mDNS.TXT:
- data := make([]byte, mDNS.Len(rr))
- _, packErr := mDNS.PackRR(rr, data, 0, nil, false)
- if packErr == nil {
- txtData = append(txtData, data)
- }
- }
- }
- canonicalName = mDNS.CanonicalName(hostname)
- canonicalType = mDNS.CanonicalName(sType)
- canonicalDomain = mDNS.CanonicalName(domain)
- return
- }
- func (t *resolve1Manager) SetLinkDNS(sender dbus.Sender, ifIndex int32, addresses []LinkDNS) *dbus.Error {
- t.linkAccess.Lock()
- defer t.linkAccess.Unlock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return wrapError(err)
- }
- link.address = addresses
- if len(addresses) > 0 {
- t.log(sender, "SetLinkDNS ", link.iif.Name, " ", strings.Join(common.Map(addresses, func(it LinkDNS) string {
- return M.AddrFromIP(it.Address).String()
- }), ", "))
- } else {
- t.log(sender, "SetLinkDNS ", link.iif.Name, " (empty)")
- }
- return t.postUpdate(link)
- }
- func (t *resolve1Manager) SetLinkDNSEx(sender dbus.Sender, ifIndex int32, addresses []LinkDNSEx) *dbus.Error {
- t.linkAccess.Lock()
- defer t.linkAccess.Unlock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return wrapError(err)
- }
- link.addressEx = addresses
- if len(addresses) > 0 {
- t.log(sender, "SetLinkDNSEx ", link.iif.Name, " ", strings.Join(common.Map(addresses, func(it LinkDNSEx) string {
- return M.SocksaddrFrom(M.AddrFromIP(it.Address), it.Port).String()
- }), ", "))
- } else {
- t.log(sender, "SetLinkDNSEx ", link.iif.Name, " (empty)")
- }
- return t.postUpdate(link)
- }
- func (t *resolve1Manager) SetLinkDomains(sender dbus.Sender, ifIndex int32, domains []LinkDomain) *dbus.Error {
- t.linkAccess.Lock()
- defer t.linkAccess.Unlock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return wrapError(err)
- }
- link.domain = domains
- if len(domains) > 0 {
- t.log(sender, "SetLinkDomains ", link.iif.Name, " ", strings.Join(common.Map(domains, func(domain LinkDomain) string {
- if !domain.RoutingOnly {
- return domain.Domain
- } else {
- return "~" + domain.Domain
- }
- }), ", "))
- } else {
- t.log(sender, "SetLinkDomains ", link.iif.Name, " (empty)")
- }
- return t.postUpdate(link)
- }
- func (t *resolve1Manager) SetLinkDefaultRoute(sender dbus.Sender, ifIndex int32, defaultRoute bool) *dbus.Error {
- t.linkAccess.Lock()
- defer t.linkAccess.Unlock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return err
- }
- link.defaultRoute = defaultRoute
- if defaultRoute {
- t.defaultRouteSequence = append(common.Filter(t.defaultRouteSequence, func(it int32) bool { return it != ifIndex }), ifIndex)
- } else {
- t.defaultRouteSequence = common.Filter(t.defaultRouteSequence, func(it int32) bool { return it != ifIndex })
- }
- var defaultRouteString string
- if defaultRoute {
- defaultRouteString = "yes"
- } else {
- defaultRouteString = "no"
- }
- t.log(sender, "SetLinkDefaultRoute ", link.iif.Name, " ", defaultRouteString)
- return t.postUpdate(link)
- }
- func (t *resolve1Manager) SetLinkLLMNR(ifIndex int32, llmnrMode string) *dbus.Error {
- return nil
- }
- func (t *resolve1Manager) SetLinkMulticastDNS(ifIndex int32, mdnsMode string) *dbus.Error {
- return nil
- }
- func (t *resolve1Manager) SetLinkDNSOverTLS(sender dbus.Sender, ifIndex int32, dotMode string) *dbus.Error {
- t.linkAccess.Lock()
- defer t.linkAccess.Unlock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return wrapError(err)
- }
- switch dotMode {
- case "yes":
- link.dnsOverTLS = true
- case "":
- dotMode = "no"
- fallthrough
- case "opportunistic", "no":
- link.dnsOverTLS = false
- }
- t.log(sender, "SetLinkDNSOverTLS ", link.iif.Name, " ", dotMode)
- return t.postUpdate(link)
- }
- func (t *resolve1Manager) SetLinkDNSSEC(ifIndex int32, dnssecMode string) *dbus.Error {
- return nil
- }
- func (t *resolve1Manager) SetLinkDNSSECNegativeTrustAnchors(ifIndex int32, domains []string) *dbus.Error {
- return nil
- }
- func (t *resolve1Manager) RevertLink(sender dbus.Sender, ifIndex int32) *dbus.Error {
- t.linkAccess.Lock()
- defer t.linkAccess.Unlock()
- link, err := t.getLink(ifIndex)
- if err != nil {
- return wrapError(err)
- }
- delete(t.links, ifIndex)
- t.log(sender, "RevertLink ", link.iif.Name)
- return t.postUpdate(link)
- }
- // TODO: implement RegisterService, UnregisterService
- func (t *resolve1Manager) RegisterService(sender dbus.Sender, identifier string, nameTemplate string, serviceType string, port uint16, priority uint16, weight uint16, txtRecords []TXTRecord) (objectPath dbus.ObjectPath, dbusErr *dbus.Error) {
- return "", wrapError(E.New("not implemented"))
- }
- func (t *resolve1Manager) UnregisterService(sender dbus.Sender, servicePath dbus.ObjectPath) error {
- return wrapError(E.New("not implemented"))
- }
- func (t *resolve1Manager) ResetStatistics() *dbus.Error {
- return nil
- }
- func (t *resolve1Manager) FlushCaches(sender dbus.Sender) *dbus.Error {
- t.dnsRouter.ClearCache()
- t.log(sender, "FlushCaches")
- return nil
- }
- func (t *resolve1Manager) ResetServerFeatures() *dbus.Error {
- return nil
- }
- func (t *resolve1Manager) postUpdate(link *TransportLink) *dbus.Error {
- if t.updateCallback != nil {
- return wrapError(t.updateCallback(link))
- }
- return nil
- }
- func rcodeError(rcode int) *dbus.Error {
- return dbus.NewError("org.freedesktop.resolve1.DnsError."+mDNS.RcodeToString[rcode], []any{mDNS.RcodeToString[rcode]})
- }
- func wrapError(err error) *dbus.Error {
- if err == nil {
- return nil
- }
- var rcode dns.RcodeError
- if errors.As(err, &rcode) {
- return rcodeError(int(rcode))
- }
- return dbus.MakeFailedError(err)
- }
|