361 lines
9.4 KiB
Go
361 lines
9.4 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"flag"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/coreos/go-systemd/daemon"
|
|
"github.com/jedisct1/dlog"
|
|
"github.com/kardianos/service"
|
|
"golang.org/x/crypto/curve25519"
|
|
)
|
|
|
|
const AppVersion = "2.0.0beta6"
|
|
|
|
type Proxy struct {
|
|
proxyPublicKey [32]byte
|
|
proxySecretKey [32]byte
|
|
questionSizeEstimator QuestionSizeEstimator
|
|
serversInfo ServersInfo
|
|
timeout time.Duration
|
|
certRefreshDelay time.Duration
|
|
certRefreshDelayAfterFailure time.Duration
|
|
mainProto string
|
|
listenAddresses []string
|
|
daemonize bool
|
|
registeredServers []RegisteredServer
|
|
pluginBlockIPv6 bool
|
|
cache bool
|
|
cacheSize int
|
|
cacheNegTTL uint32
|
|
cacheMinTTL uint32
|
|
cacheMaxTTL uint32
|
|
queryLogFile string
|
|
queryLogFormat string
|
|
queryLogIgnoredQtypes []string
|
|
nxLogFile string
|
|
nxLogFormat string
|
|
blockNameFile string
|
|
blockNameLogFile string
|
|
blockNameFormat string
|
|
forwardFile string
|
|
pluginsGlobals PluginsGlobals
|
|
urlsToPrefetch []URLToPrefetch
|
|
}
|
|
|
|
type App struct {
|
|
wg sync.WaitGroup
|
|
quit chan struct{}
|
|
proxy Proxy
|
|
}
|
|
|
|
func main() {
|
|
dlog.Init("dnscrypt-proxy", dlog.SeverityNotice, "DAEMON")
|
|
dlog.Noticef("Starting dnscrypt-proxy %s", AppVersion)
|
|
|
|
cdLocal()
|
|
|
|
svcConfig := &service.Config{
|
|
Name: "dnscrypt-proxy",
|
|
DisplayName: "DNSCrypt client proxy",
|
|
Description: "Encrypted/authenticated DNS proxy",
|
|
}
|
|
svcFlag := flag.String("service", "", fmt.Sprintf("Control the system service: %q", service.ControlAction))
|
|
app := &App{}
|
|
svc, err := service.New(app, svcConfig)
|
|
if err != nil {
|
|
svc = nil
|
|
dlog.Debug(err)
|
|
}
|
|
app.proxy = Proxy{}
|
|
if err := ConfigLoad(&app.proxy, svcFlag, "dnscrypt-proxy.toml"); err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
if len(*svcFlag) != 0 {
|
|
if err := service.Control(svc, *svcFlag); err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
if *svcFlag == "install" {
|
|
dlog.Notice("Installed as a service. Use `-service start` to start")
|
|
} else if *svcFlag == "uninstall" {
|
|
dlog.Notice("Service uninstalled")
|
|
} else if *svcFlag == "start" {
|
|
dlog.Notice("Service started")
|
|
} else if *svcFlag == "stop" {
|
|
dlog.Notice("Service stopped")
|
|
} else if *svcFlag == "restart" {
|
|
dlog.Notice("Service restarted")
|
|
}
|
|
return
|
|
}
|
|
if svc != nil {
|
|
if err = svc.Run(); err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
} else {
|
|
app.Start(nil)
|
|
}
|
|
}
|
|
|
|
func (app *App) Start(service service.Service) error {
|
|
proxy := app.proxy
|
|
if err := InitPluginsGlobals(&proxy.pluginsGlobals, &proxy); err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
if proxy.daemonize {
|
|
Daemonize()
|
|
}
|
|
app.quit = make(chan struct{})
|
|
app.wg.Add(1)
|
|
if service != nil {
|
|
go func() {
|
|
app.AppMain(&proxy)
|
|
}()
|
|
} else {
|
|
app.AppMain(&proxy)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *App) AppMain(proxy *Proxy) {
|
|
proxy.StartProxy()
|
|
<-app.quit
|
|
dlog.Notice("Quit signal received...")
|
|
app.wg.Done()
|
|
}
|
|
|
|
func (app *App) Stop(service service.Service) error {
|
|
dlog.Notice("Stopped.")
|
|
return nil
|
|
}
|
|
|
|
func (proxy *Proxy) StartProxy() {
|
|
proxy.questionSizeEstimator = NewQuestionSizeEstimator()
|
|
if _, err := rand.Read(proxy.proxySecretKey[:]); err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
curve25519.ScalarBaseMult(&proxy.proxyPublicKey, &proxy.proxySecretKey)
|
|
for _, registeredServer := range proxy.registeredServers {
|
|
proxy.serversInfo.registerServer(proxy, registeredServer.name, registeredServer.stamp)
|
|
}
|
|
for _, listenAddrStr := range proxy.listenAddresses {
|
|
listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr)
|
|
if err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddrStr)
|
|
if err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
if err := proxy.udpListener(listenUDPAddr); err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
if err := proxy.tcpListener(listenTCPAddr); err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
}
|
|
liveServers, err := proxy.serversInfo.refresh(proxy)
|
|
if liveServers > 0 {
|
|
dlog.Noticef("dnscrypt-proxy is ready - live servers: %d", liveServers)
|
|
daemon.SdNotify(false, "READY=1")
|
|
} else if err != nil {
|
|
dlog.Error(err)
|
|
dlog.Notice("dnscrypt-proxy is waiting for at least one server to be reachable")
|
|
}
|
|
proxy.prefetcher(&proxy.urlsToPrefetch)
|
|
go func() {
|
|
for {
|
|
delay := proxy.certRefreshDelay
|
|
if proxy.serversInfo.liveServers() == 0 {
|
|
delay = proxy.certRefreshDelayAfterFailure
|
|
}
|
|
time.Sleep(delay)
|
|
proxy.serversInfo.refresh(proxy)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (proxy *Proxy) prefetcher(urlsToPrefetch *[]URLToPrefetch) {
|
|
go func() {
|
|
for {
|
|
now := time.Now()
|
|
for i := range *urlsToPrefetch {
|
|
urlToPrefetch := &(*urlsToPrefetch)[i]
|
|
if now.After(urlToPrefetch.when) {
|
|
dlog.Debugf("Prefetching [%s]", urlToPrefetch.url)
|
|
if err := PrefetchSourceURL(urlToPrefetch); err != nil {
|
|
dlog.Debugf("Prefetching [%s] failed: %s", err)
|
|
} else {
|
|
dlog.Debugf("Prefetching [%s] succeeded. Next refresh scheduled for %v", urlToPrefetch.url, urlToPrefetch.when)
|
|
}
|
|
}
|
|
}
|
|
time.Sleep(60 * time.Second)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (proxy *Proxy) udpListener(listenAddr *net.UDPAddr) error {
|
|
clientPc, err := net.ListenUDP("udp", listenAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go func() {
|
|
defer clientPc.Close()
|
|
dlog.Noticef("Now listening to %v [UDP]", listenAddr)
|
|
for {
|
|
buffer := make([]byte, MaxDNSPacketSize-1)
|
|
length, clientAddr, err := clientPc.ReadFrom(buffer)
|
|
if err != nil {
|
|
return
|
|
}
|
|
packet := buffer[:length]
|
|
go func() {
|
|
proxy.processIncomingQuery(proxy.serversInfo.getOne(), "udp", proxy.mainProto, packet, &clientAddr, clientPc)
|
|
}()
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (proxy *Proxy) tcpListener(listenAddr *net.TCPAddr) error {
|
|
acceptPc, err := net.ListenTCP("tcp", listenAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go func() {
|
|
defer acceptPc.Close()
|
|
dlog.Noticef("Now listening to %v [TCP]", listenAddr)
|
|
for {
|
|
clientPc, err := acceptPc.Accept()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
go func() {
|
|
defer clientPc.Close()
|
|
clientPc.SetDeadline(time.Now().Add(proxy.timeout))
|
|
packet, err := ReadPrefixed(clientPc.(*net.TCPConn))
|
|
if err != nil || len(packet) < MinDNSPacketSize {
|
|
return
|
|
}
|
|
clientAddr := clientPc.RemoteAddr()
|
|
proxy.processIncomingQuery(proxy.serversInfo.getOne(), "tcp", "tcp", packet, &clientAddr, clientPc)
|
|
}()
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, encryptedQuery []byte, clientNonce []byte) ([]byte, error) {
|
|
pc, err := net.DialUDP("udp", nil, serverInfo.UDPAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pc.SetDeadline(time.Now().Add(serverInfo.Timeout))
|
|
pc.Write(encryptedQuery)
|
|
encryptedResponse := make([]byte, MaxDNSPacketSize)
|
|
length, err := pc.Read(encryptedResponse)
|
|
pc.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
encryptedResponse = encryptedResponse[:length]
|
|
return proxy.Decrypt(serverInfo, encryptedResponse, clientNonce)
|
|
}
|
|
|
|
func (proxy *Proxy) exchangeWithTCPServer(serverInfo *ServerInfo, encryptedQuery []byte, clientNonce []byte) ([]byte, error) {
|
|
pc, err := net.DialTCP("tcp", nil, serverInfo.TCPAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pc.SetDeadline(time.Now().Add(serverInfo.Timeout))
|
|
encryptedQuery, err = PrefixWithSize(encryptedQuery)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pc.Write(encryptedQuery)
|
|
|
|
encryptedResponse, err := ReadPrefixed(pc)
|
|
pc.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return proxy.Decrypt(serverInfo, encryptedResponse, clientNonce)
|
|
}
|
|
|
|
func (proxy *Proxy) processIncomingQuery(serverInfo *ServerInfo, clientProto string, serverProto string, query []byte, clientAddr *net.Addr, clientPc net.Conn) {
|
|
if len(query) < MinDNSPacketSize || serverInfo == nil {
|
|
return
|
|
}
|
|
pluginsState := NewPluginsState(proxy, clientProto, clientAddr)
|
|
query, _ = pluginsState.ApplyQueryPlugins(&proxy.pluginsGlobals, query)
|
|
var response []byte
|
|
var err error
|
|
if pluginsState.action != PluginsActionForward {
|
|
if pluginsState.synthResponse != nil {
|
|
response, err = pluginsState.synthResponse.PackBuffer(response)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
if pluginsState.action == PluginsActionDrop {
|
|
return
|
|
}
|
|
}
|
|
if len(response) == 0 {
|
|
encryptedQuery, clientNonce, err := proxy.Encrypt(serverInfo, query, serverProto)
|
|
if err != nil {
|
|
return
|
|
}
|
|
serverInfo.noticeBegin(proxy)
|
|
if serverProto == "udp" {
|
|
response, err = proxy.exchangeWithUDPServer(serverInfo, encryptedQuery, clientNonce)
|
|
} else {
|
|
response, err = proxy.exchangeWithTCPServer(serverInfo, encryptedQuery, clientNonce)
|
|
}
|
|
if err != nil {
|
|
serverInfo.noticeFailure(proxy)
|
|
return
|
|
}
|
|
response, _ = pluginsState.ApplyResponsePlugins(&proxy.pluginsGlobals, response)
|
|
}
|
|
if clientProto == "udp" {
|
|
if len(response) > MaxDNSUDPPacketSize {
|
|
response, err = TruncatedResponse(response)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
clientPc.(net.PacketConn).WriteTo(response, *clientAddr)
|
|
if HasTCFlag(response) {
|
|
proxy.questionSizeEstimator.blindAdjust()
|
|
} else {
|
|
proxy.questionSizeEstimator.adjust(ResponseOverhead + len(response))
|
|
}
|
|
} else {
|
|
response, err = PrefixWithSize(response)
|
|
if err != nil {
|
|
serverInfo.noticeFailure(proxy)
|
|
return
|
|
}
|
|
clientPc.Write(response)
|
|
}
|
|
serverInfo.noticeSuccess(proxy)
|
|
}
|
|
|
|
func cdLocal() {
|
|
ex, err := os.Executable()
|
|
if err != nil {
|
|
dlog.Critical(err)
|
|
return
|
|
}
|
|
exPath := filepath.Dir(ex)
|
|
os.Chdir(exPath)
|
|
}
|