func (p *Proxy) doStart() bool {
	p.mu.Lock()
	defer p.mu.Unlock()
	switch p.stateLocked() {
	case StateStarting, StateReasserting:
		p.setStateLocked(StateStarted)
		return true
	default:
		return false
	}
}
doStart transitions to StateStarted. If the previous state wasn't
StateStarting or StateReassessing, no transition happens and false is
returned.
package proxy
import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"
	"github.com/nextdns/nextdns/resolver/endpoint"
	tun "github.com/nextdns/windows/tun"
)
const (
	StateStopped     = "stopped"
	StateStarting    = "starting"
	StateStarted     = "started"
	StateReasserting = "reasserting"
	StateStopping    = "stopping"
)
type Proxy struct {
	ExtraHeaders http.Header
	OnStateChange func(state string)
	QueryLog func(msgID uint16, qname string)
	ErrorLog func(error)
	InfoLog func(string)
	manager *endpoint.Manager
	hostname string
	id       string
	mu    sync.Mutex
	tun   io.ReadWriteCloser
	state string
	stop  chan struct{}
	dedup dedup
}
func (p *Proxy) SetUpstreamHostName(hostname string) {
	p.hostname = hostname
}
func (p *Proxy) SetConfigID(id string) {
	p.id = id
}
func (p *Proxy) SetDeviceInfo(name, model, id, version string) {
	reportHdr := p.ExtraHeaders
	if reportHdr == nil {
		reportHdr = http.Header{}
		p.ExtraHeaders = reportHdr
	}
	if name != "" {
		reportHdr.Set("X-Device-Name", name)
	}
	if model != "" {
		reportHdr.Set("X-Device-Model", model)
	}
	if id != "" {
		reportHdr.Set("X-Device-Id", id)
	}
	reportHdr.Set("User-Agent", "nextdns-windows/"+version)
}
func (p *Proxy) State() string {
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.stateLocked()
}
func (p *Proxy) stateLocked() string {
	if p.state == "" {
		return StateStopped
	}
	return p.state
}
func (p *Proxy) setStateLocked(s string) {
	if p.state == s {
		return
	}
	p.state = s
	if p.OnStateChange != nil {
		p.OnStateChange(s)
	}
}
func (p *Proxy) Start() (err error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if p.stateLocked() != StateStopped {
		return nil  
	}
	p.setStateLocked(StateStarting)
	return p.startLocked()
}
func (p *Proxy) startLocked() (err error) {
	if p.tun, err = tun.OpenTunDevice("tun0", "192.0.2.43", "192.0.2.42", "255.255.255.0", []string{"192.0.2.42"}); err != nil {
		return err
	}
	p.manager = p.nextdnsManager()
	go p.run()
	return nil
}
func (p *Proxy) nextdnsManager() *endpoint.Manager {
	hostname := p.hostname
	if hostname == "" {
		hostname = "windows.dns.nextdns.io"
	}
	return &endpoint.Manager{
		Providers: []endpoint.Provider{
			&endpoint.SourceHTTPSSVCProvider{
				Hostname: hostname,
				Source:   endpoint.MustNew(fmt.Sprintf("https://%s#45.90.28.0,2a07:a8c0::,45.90.30.0,2a07:a8c1::", hostname)),
			},
			endpoint.StaticProvider([]endpoint.Endpoint{
				endpoint.MustNew("https://dns1.nextdns.io#45.90.28.0,2a07:a8c0::"),
				endpoint.MustNew("https://dns2.nextdns.io#45.90.30.0,2a07:a8c1::"),
			}),
		},
		InitEndpoint: endpoint.MustNew(fmt.Sprintf("https://%s#45.90.28.0,2a07:a8c0::,45.90.30.0,2a07:a8c1::", hostname)),
		OnError: func(e endpoint.Endpoint, err error) {
			if p.ErrorLog != nil {
				p.ErrorLog(fmt.Errorf("Endpoint failed: %s: %v", e, err))
			}
		},
		OnProviderError: func(pr endpoint.Provider, err error) {
			p.ErrorLog(fmt.Errorf("Endpoint provider failed: %v: %v", pr, err))
		},
		OnConnect: func(ci *endpoint.ConnectInfo) {
			p.InfoLog(fmt.Sprintf("Connected %s (con=%dms tls=%dms, %s, %s)",
				ci.ServerAddr,
				ci.ConnectTimes[ci.ServerAddr]/time.Millisecond,
				ci.TLSTime/time.Millisecond,
				ci.Protocol,
				ci.TLSVersion))
		},
		OnChange: func(e endpoint.Endpoint) {
			if p.InfoLog != nil {
				p.InfoLog(fmt.Sprintf("Switching endpoint: %s", e))
			}
		},
	}
}
func (p *Proxy) Stop() (err error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	switch p.stateLocked() {
	case StateStopped, StateStarting:
		return nil  
	}
	p.setStateLocked(StateStopping)
	if p.tun != nil {
		err = p.tun.Close()
		p.tun = nil
	}
	if p.stop != nil {
		close(p.stop)
		p.stop = nil
	}
	p.manager = nil
	return err
}
func (p *Proxy) restartOrStop() {
	p.mu.Lock()
	defer p.mu.Unlock()
	switch p.stateLocked() {
	case StateStopping:
		p.setStateLocked(StateStopped)
		return
	case StateStopped:
		return
	}
	p.setStateLocked(StateReasserting)
	for {
		time.Sleep(5 * time.Second)
		if err := p.startLocked(); err != nil {
			p.logErr(fmt.Errorf("restart err: %v", err))
			continue
		}
		break
	}
}