mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-20 17:58:02 +02:00
Update port, ip choice logic in DNS service (#1514)
Ensure we use WG address instead of loopback addresses for eBPF. - First try to use 53 port - Try to use 5053 port on WG interface for eBPF - Try to use 5053 on WG interface or loopback interface
This commit is contained in:
parent
8604add997
commit
f89332fcd2
@ -28,7 +28,7 @@ type serviceViaListener struct {
|
|||||||
customAddr *netip.AddrPort
|
customAddr *netip.AddrPort
|
||||||
server *dns.Server
|
server *dns.Server
|
||||||
listenIP string
|
listenIP string
|
||||||
listenPort int
|
listenPort uint16
|
||||||
listenerIsRunning bool
|
listenerIsRunning bool
|
||||||
listenerFlagLock sync.Mutex
|
listenerFlagLock sync.Mutex
|
||||||
ebpfService ebpfMgr.Manager
|
ebpfService ebpfMgr.Manager
|
||||||
@ -66,15 +66,6 @@ func (s *serviceViaListener) Listen() error {
|
|||||||
return fmt.Errorf("eval listen address: %w", err)
|
return fmt.Errorf("eval listen address: %w", err)
|
||||||
}
|
}
|
||||||
s.server.Addr = fmt.Sprintf("%s:%d", s.listenIP, s.listenPort)
|
s.server.Addr = fmt.Sprintf("%s:%d", s.listenIP, s.listenPort)
|
||||||
|
|
||||||
if s.shouldApplyPortFwd() {
|
|
||||||
s.ebpfService = ebpf.GetEbpfManagerInstance()
|
|
||||||
err = s.ebpfService.LoadDNSFwd(s.listenIP, s.listenPort)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to load DNS port forwarder, custom port may not work well on some Linux operating systems: %s", err)
|
|
||||||
s.ebpfService = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Debugf("starting dns on %s", s.server.Addr)
|
log.Debugf("starting dns on %s", s.server.Addr)
|
||||||
go func() {
|
go func() {
|
||||||
s.setListenerStatus(true)
|
s.setListenerStatus(true)
|
||||||
@ -128,7 +119,7 @@ func (s *serviceViaListener) RuntimePort() int {
|
|||||||
if s.ebpfService != nil {
|
if s.ebpfService != nil {
|
||||||
return defaultPort
|
return defaultPort
|
||||||
} else {
|
} else {
|
||||||
return s.listenPort
|
return int(s.listenPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,55 +131,112 @@ func (s *serviceViaListener) setListenerStatus(running bool) {
|
|||||||
s.listenerIsRunning = running
|
s.listenerIsRunning = running
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceViaListener) getFirstDNSListenerAvailable() (string, int, error) {
|
// evalListenAddress figure out the listen address for the DNS server
|
||||||
ips := []string{defaultIP, customIP}
|
// first check the 53 port availability on WG interface or lo, if not success
|
||||||
|
// pick a random port on WG interface for eBPF, if not success
|
||||||
|
// check the 5053 port availability on WG interface or lo without eBPF usage,
|
||||||
|
func (s *serviceViaListener) evalListenAddress() (string, uint16, error) {
|
||||||
|
if s.customAddr != nil {
|
||||||
|
return s.customAddr.Addr().String(), s.customAddr.Port(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, ok := s.testFreePort(defaultPort)
|
||||||
|
if ok {
|
||||||
|
return ip, defaultPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ebpfSrv, port, ok := s.tryToUseeBPF()
|
||||||
|
if ok {
|
||||||
|
s.ebpfService = ebpfSrv
|
||||||
|
return s.wgInterface.Address().IP.String(), port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, ok = s.testFreePort(customPort)
|
||||||
|
if ok {
|
||||||
|
return ip, customPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", 0, fmt.Errorf("failed to find a free port for DNS server")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceViaListener) testFreePort(port int) (string, bool) {
|
||||||
|
var ips []string
|
||||||
if runtime.GOOS != "darwin" {
|
if runtime.GOOS != "darwin" {
|
||||||
ips = append([]string{s.wgInterface.Address().IP.String()}, ips...)
|
ips = []string{s.wgInterface.Address().IP.String(), defaultIP, customIP}
|
||||||
|
} else {
|
||||||
|
ips = []string{defaultIP, customIP}
|
||||||
}
|
}
|
||||||
ports := []int{defaultPort, customPort}
|
|
||||||
for _, port := range ports {
|
for _, ip := range ips {
|
||||||
for _, ip := range ips {
|
if !s.tryToBind(ip, port) {
|
||||||
addrString := fmt.Sprintf("%s:%d", ip, port)
|
continue
|
||||||
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString))
|
|
||||||
probeListener, err := net.ListenUDP("udp", udpAddr)
|
|
||||||
if err == nil {
|
|
||||||
err = probeListener.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("got an error closing the probe listener, error: %s", err)
|
|
||||||
}
|
|
||||||
log.Infof("successfully found dns binding candidate at %s", addrString)
|
|
||||||
return ip, port, nil
|
|
||||||
}
|
|
||||||
log.Debugf("binding dns on %s is not available, error: %s", addrString, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ip, true
|
||||||
}
|
}
|
||||||
return "", 0, fmt.Errorf("unable to find an unused ip and port combination. IPs tested: %v and ports %v", ips, ports)
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceViaListener) evalListenAddress() (string, int, error) {
|
func (s *serviceViaListener) tryToBind(ip string, port int) bool {
|
||||||
if s.customAddr != nil {
|
addrString := fmt.Sprintf("%s:%d", ip, port)
|
||||||
return s.customAddr.Addr().String(), int(s.customAddr.Port()), nil
|
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString))
|
||||||
}
|
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||||
|
if err != nil {
|
||||||
return s.getFirstDNSListenerAvailable()
|
log.Warnf("binding dns on %s is not available, error: %s", addrString, err)
|
||||||
}
|
|
||||||
|
|
||||||
// shouldApplyPortFwd decides whether to apply eBPF program to capture DNS traffic on port 53.
|
|
||||||
// This is needed because on some operating systems if we start a DNS server not on a default port 53, the domain name
|
|
||||||
// resolution won't work.
|
|
||||||
// So, in case we are running on Linux and picked a non-default port (53) we should fall back to the eBPF solution that will capture
|
|
||||||
// traffic on port 53 and forward it to a local DNS server running on 5053.
|
|
||||||
func (s *serviceViaListener) shouldApplyPortFwd() bool {
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.customAddr != nil {
|
err = probeListener.Close()
|
||||||
return false
|
if err != nil {
|
||||||
}
|
log.Errorf("got an error closing the probe listener, error: %s", err)
|
||||||
|
|
||||||
if s.listenPort == defaultPort {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryToUseeBPF decides whether to apply eBPF program to capture DNS traffic on port 53.
|
||||||
|
// This is needed because on some operating systems if we start a DNS server not on a default port 53,
|
||||||
|
// the domain name resolution won't work. So, in case we are running on Linux and picked a free
|
||||||
|
// port we should fall back to the eBPF solution that will capture traffic on port 53 and forward
|
||||||
|
// it to a local DNS server running on the chosen port.
|
||||||
|
func (s *serviceViaListener) tryToUseeBPF() (ebpfMgr.Manager, uint16, bool) {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := s.generateFreePort() //nolint:staticcheck,unused
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to generate a free port for eBPF DNS forwarder server: %s", err)
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ebpfSrv := ebpf.GetEbpfManagerInstance()
|
||||||
|
err = ebpfSrv.LoadDNSFwd(s.wgInterface.Address().IP.String(), int(port))
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to load DNS forwarder eBPF program, error: %s", err)
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ebpfSrv, port, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceViaListener) generateFreePort() (uint16, error) {
|
||||||
|
ok := s.tryToBind(s.wgInterface.Address().IP.String(), customPort)
|
||||||
|
if ok {
|
||||||
|
return customPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort("0.0.0.0:0"))
|
||||||
|
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to bind random port for DNS: %s", err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrPort := netip.MustParseAddrPort(probeListener.LocalAddr().String()) // might panic if address is incorrect
|
||||||
|
err = probeListener.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to free up DNS port: %s", err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return addrPort.Port(), nil
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (tf *GeneralManager) LoadDNSFwd(ip string, dnsPort int) error {
|
func (tf *GeneralManager) LoadDNSFwd(ip string, dnsPort int) error {
|
||||||
log.Debugf("load ebpf DNS forwarder: address: %s:%d", ip, dnsPort)
|
log.Debugf("load eBPF DNS forwarder, watching addr: %s:53, redirect to port: %d", ip, dnsPort)
|
||||||
tf.lock.Lock()
|
tf.lock.Lock()
|
||||||
defer tf.lock.Unlock()
|
defer tf.lock.Unlock()
|
||||||
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
# Debug
|
# DNS forwarder
|
||||||
|
|
||||||
|
The agent attach the XDP program to the lo device. We can not use fake address in eBPF because the
|
||||||
|
traffic does not appear in the eBPF program. The program capture the traffic on wg_ip:53 and
|
||||||
|
overwrite in it the destination port to 5053.
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
|
||||||
The CONFIG_BPF_EVENTS kernel module is required for bpf_printk.
|
The CONFIG_BPF_EVENTS kernel module is required for bpf_printk.
|
||||||
Apply this code to use bpf_printk
|
Apply this code to use bpf_printk
|
||||||
|
Loading…
x
Reference in New Issue
Block a user