Files
desktop-edge-win/service/cziti/dns-proxy.go
T
dovholuknf 2f9a9ee351 comment out all the tun device/map lookups. only one tun ever exists at this time
fix all panics --> log.Panicf

eliminate very similar function calls AddIntercept

fix issue with lookups not considering the port when looking for duplicate definitions

rename DeregisterService to UnregisterService
2020-10-16 13:23:13 -04:00

310 lines
8.5 KiB
Go

/*
* Copyright NetFoundry, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cziti
import (
"fmt"
"github.com/miekg/dns"
"io"
"net"
"os"
"strings"
"time"
)
var domains []string // get any connection-specific local domains
const MaxDnsRequests = 64
var reqch = make(chan dnsreq, MaxDnsRequests)
var proxiedRequests = make(chan *proxiedReq, MaxDnsRequests)
var respChan = make(chan []byte, MaxDnsRequests)
func processDNSquery(packet []byte, p *net.UDPAddr, s *net.UDPConn, ipVer int) {
q := &dns.Msg{}
if err := q.Unpack(packet); err != nil {
log.Errorf("ERROR", err)
return
}
msg := dns.Msg{}
msg.SetReply(q)
msg.RecursionAvailable = false
msg.Authoritative = false
msg.Rcode = dns.RcodeRefused
query := q.Question[0]
log.Tracef("processing a dns query. type:%s, for:%s on %v. id:%v", dns.Type(query.Qtype), query.Name, p, q.Id)
var ip net.IP
// fmt.Printf("query: Type(%d) name(%s)\n", query.Qtype, query.Name)
dnsName := strings.TrimSpace(query.Name)
ip = DNS.Resolve(dnsName)
// never proxy hostnames that we know about regardless of type
if ip == nil {
// no direct hit. need to now check to see if the dns query used a connection-specific local domain
for _, d := range domains {
domain := d
if strings.HasSuffix(dnsName, domain) {
dnsNameTrimmed := strings.TrimRight(dnsName, domain)
// dns request has domain appended - removing and resolving
ip = DNS.Resolve(dnsNameTrimmed)
break
}
}
}
// never proxy hostnames that we know about regardless of type
if ip != nil {
log.Debugf("resolved %s as %v", query.Name, ip)
var answer *dns.A
if query.Qtype == dns.TypeA && len(ip.To4()) == net.IPv4len {
answer = &dns.A{
Hdr: dns.RR_Header{Name: query.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60},
A: ip,
}
} else if query.Qtype == dns.TypeAAAA {
answer = &dns.A{
Hdr: dns.RR_Header{Name: query.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 60},
A: ip.To16(),
}
}
if answer != nil {
msg.Authoritative = true
msg.Rcode = dns.RcodeSuccess
msg.Answer = append(msg.Answer, answer)
}
repB, err := msg.Pack()
if err == nil {
_, _, err = s.WriteMsgUDP(repB, nil, p)
}
if err != nil {
log.Error("unexpected dns error", err)
}
} else {
// log.Debug("proxying ", dns.Type(query.Qtype), query.Name, q.Id, " for ", p)
proxyDNS(q, p, s, ipVer)
}
}
type dnsreq struct {
q []byte
s *net.UDPConn
p *net.UDPAddr
ifId int
}
func RunDNSserver(dnsBind []net.IP, ready chan bool) {
dnsServers := GetUpstreamDNS()
go runDNSproxy(dnsServers)
for _, bindAddr := range dnsBind {
go runListener(bindAddr, 53, reqch)
}
ReplaceDNS(dnsBind)
ready <- true
for req := range reqch {
processDNSquery(req.q, req.p, req.s, req.ifId)
}
}
func runListener(ip net.IP, port int, reqch chan dnsreq) {
log.Infof("Running DNS listener on %v", ip)
laddr := &net.UDPAddr{
IP: ip,
Port: port,
Zone: "",
}
id := 6
network := "udp6"
if len(ip.To4()) == net.IPv4len {
id = 4
network = "udp4"
}
server, err := net.ListenUDP(network, laddr)
if err != nil {
log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "udp listening on network", err)
}
for {
b := make([]byte, 1024)
nb, _, _, peer, err := server.ReadMsgUDP(b, nil)
if err != nil {
if err == io.EOF || err == os.ErrClosed || strings.HasSuffix(err.Error(), "use of closed network connection") {
log.Warnf("DNS listener closing.")
_ = server.Close()
break
} else {
_ = server.Close()
log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "reading a udp message", err)
}
}
reqch <- dnsreq{
q: b[:nb],
s: server,
p: peer,
ifId: id,
}
}
}
/*******************************************************************/
type proxiedReq struct {
req *dns.Msg
peer *net.UDPAddr
s *net.UDPConn
exp time.Time
ipVer int
}
func proxyDNS(req *dns.Msg, peer *net.UDPAddr, serv *net.UDPConn, ipVer int) {
proxiedRequests <- &proxiedReq{
req: req,
peer: peer,
s: serv,
exp: time.Now().Add(30 * time.Second),
ipVer: ipVer,
}
}
func dnsPanicRecover() {
// get dns again and reconfigure
go runDNSproxy(GetUpstreamDNS())
}
func runDNSproxy(dnsServers []string) {
domains = GetConnectionSpecificDomains()
log.Infof("ConnectionSpecificDomains: %v", domains)
log.Infof("dnsServers: %v", dnsServers)
defer func() {
if err := recover(); err != nil {
log.Errorf("Recovering from panic due to DNS-related issue. %v", err)
dnsPanicRecover()
}
}()
var dnsUpstreams []*net.UDPConn
for _, s := range dnsServers {
sAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:53", s))
if err != nil {
// fec0:0:0:ffff:: is 'legacy' from windows apparently...
// see: https://en.wikipedia.org/wiki/IPv6_address#Deprecated_and_obsolete_addresses_2
if ! strings.HasPrefix(s, "fec0:0:0:ffff::") {
log.Errorf("skipping upstream due to error: %s, %v", s, err.Error())
} else {
// just ignore for now - don't even log it...
}
} else {
log.Debugf("adding upstream dns server: %s", s)
conn, err := net.DialUDP("udp", nil, sAddr)
if err != nil {
log.Debugf("skipping upstream due to dial udp issue to %s. error: %v", s, err.Error())
} else {
dnsUpstreams = append(dnsUpstreams, conn)
}
}
}
for _, proxy := range dnsUpstreams {
go func(p *net.UDPConn) {
resp := make([]byte, 1024) //make a buffer which is reused
for {
n, err := p.Read(resp)
if err != nil {
// something is wrong with the DNS connection panic and let DNS recovery kick in
if err == io.EOF || err == os.ErrClosed || strings.HasSuffix(err.Error(), "use of closed network connection") {
log.Errorf("error receiving from ip: %v. error %v", p.RemoteAddr(), err)
return
} else {
log.Warnf("odd error: %v", err)
}
} else {
respChan <- resp[:n]
}
}
}(proxy)
}
reqs := make(map[uint32]*proxiedReq)
for {
select {
case pr := <-proxiedRequests:
id := (uint32(pr.req.Id) << 16) | uint32(pr.req.Question[0].Qtype)
reqs[id] = pr
b, _ := pr.req.Pack()
for _, proxy := range dnsUpstreams {
if _, err := proxy.Write(b); err != nil {
_ = proxy.Close() //first thing - close the proxy connection
// when this hits - it never seems to recover. throw a panic which will get recovered
// via the defer specified above and try to recreate the connections. this is a heavy
// handed way of reconnecting to the DNS server but it should be infrequent
log.Panicf("failed to proxy DNS to %s %s %v. %v",
dns.Type(pr.req.Question[0].Qtype),
pr.req.Question[0].Name,
proxy.RemoteAddr(),
err)
} else {
log.Tracef("Proxied request sent to %v from ipv%d listener", proxy.RemoteAddr(), pr.ipVer)
}
}
case rep := <-respChan:
reply := dns.Msg{}
if err := reply.Unpack(rep); err == nil {
id := (uint32(reply.Id) << 16) | uint32(reply.Question[0].Qtype)
req, found := reqs[id]
if found {
delete(reqs, id)
log.Tracef("proxy resolved request for %v id:%d", reply.Question[0].Name, reply.Id)
n, oobn, err := req.s.WriteMsgUDP(rep, nil, req.peer)
if err != nil {
log.Errorf("an error has occurred while trying to write a udp message. n:%d, oobn:%d, err:%v", n, oobn, err)
}
} else {
// keep this log but leave commented out. When two listeners are enabled (ipv4/v6) this msg will
// just mean some other request was processed successfully and removed the entry from the map
//log.Tracef("matching request was not found for id:%d. %s %s", reply.Id, dns.Type(reply.Question[0].Qtype), reply.Question[0].Name)
}
}
case <-time.After(time.Minute):
// cleanup requests we didn't get answers for
now := time.Now()
for k, r := range reqs {
if now.After(r.exp) {
log.Debugf("a DNS request has expired - enable trace logging and reproduce this issue for more information")
log.Tracef(" expired DNS req: %s %s", dns.Type(r.req.Question[0].Qtype), r.req.Question[0].Name)
delete(reqs, k)
}
}
}
}
}