mirror of
https://github.com/norouter/norouter.git
synced 2025-12-24 13:17:54 +08:00
Add built-in DNS
e.g. ```console $ dig -p 10053 +tcp example.com @127.0.42.100 ``` Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
1
go.mod
1
go.mod
@@ -9,6 +9,7 @@ require (
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
|
||||
6
go.sum
6
go.sum
@@ -180,6 +180,8 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN
|
||||
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -268,6 +270,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -303,6 +306,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
||||
@@ -336,6 +340,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -376,6 +381,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201021000207-d49c4edd7d96/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -26,8 +26,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/norouter/norouter/pkg/agent/bicopy"
|
||||
"github.com/norouter/norouter/pkg/agent/bicopy/bicopyutil"
|
||||
agentdns "github.com/norouter/norouter/pkg/agent/dns"
|
||||
"github.com/norouter/norouter/pkg/agent/etchosts"
|
||||
agenthttp "github.com/norouter/norouter/pkg/agent/http"
|
||||
"github.com/norouter/norouter/pkg/agent/loopback"
|
||||
@@ -179,6 +181,10 @@ func (a *Agent) configure(args *jsonmsg.ConfigureRequestArgs) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.configureDNS(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.router, err = router.New(a.config.Routes)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -214,6 +220,39 @@ func (a *Agent) configure(args *jsonmsg.ConfigureRequestArgs) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) configureDNS() error {
|
||||
var dnsSrv *dns.Server
|
||||
for _, f := range a.config.NameServers {
|
||||
if f.IP.Equal(a.config.Me) {
|
||||
if f.Proto != "tcp" {
|
||||
return errors.Errorf("expected \"tcp\", got %q as the built-in DNS port", f.Proto)
|
||||
}
|
||||
logrus.Debugf("dns virtual TCP port=%d", f.Port)
|
||||
if dnsSrv != nil {
|
||||
return errors.New("duplicated DNS?")
|
||||
}
|
||||
var err error
|
||||
dnsSrv, err = agentdns.New(a.stack, a.config.Me, int(f.Port), a.config.HostnameMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !a.config.Loopback.Disable {
|
||||
if err := loopback.GoOther(a.stack, f.IPPortProto); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if dnsSrv != nil {
|
||||
go func() {
|
||||
if e := dnsSrv.ActivateAndServe(); e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) configureHTTP() error {
|
||||
logrus.Debugf("http listen=%q", a.config.HTTP.Listen)
|
||||
l, err := net.Listen("tcp", a.config.HTTP.Listen)
|
||||
|
||||
191
pkg/agent/dns/dns.go
Normal file
191
pkg/agent/dns/dns.go
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
Copyright (C) NoRouter authors.
|
||||
|
||||
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
|
||||
|
||||
http://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 dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
func New(st *stack.Stack, vip net.IP, tcpPort int, hostnameMap map[string]net.IP) (*dns.Server, error) {
|
||||
h, err := NewHandler(hostnameMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullAddr := tcpip.FullAddress{
|
||||
Addr: tcpip.Address(vip),
|
||||
Port: uint16(tcpPort),
|
||||
}
|
||||
l, err := gonet.ListenTCP(st, fullAddr, ipv4.ProtocolNumber)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to listen on %q", fullAddr)
|
||||
}
|
||||
srv := &dns.Server{
|
||||
Handler: h,
|
||||
Listener: l,
|
||||
}
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
func NewClientConfig() (*dns.ClientConfig, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return newClientConfigWindows()
|
||||
}
|
||||
return dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
}
|
||||
|
||||
func newClientConfigWindows() (*dns.ClientConfig, error) {
|
||||
powershell, err := exec.LookPath("powershell.exe")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args := []string{"-NoProfile", "-NonInteractive", "Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -ExpandProperty ServerAddresses"}
|
||||
logrus.Debugf("executing %v", append([]string{powershell}, args...))
|
||||
out, err := exec.Command(powershell, args...).Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
var ips []net.IP
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
ip := net.ParseIP(line)
|
||||
if ip == nil {
|
||||
logrus.Warnf("unexpected line from Powershell output: %q", line)
|
||||
continue
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse Powershell output")
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return nil, errors.New("no DNS found")
|
||||
}
|
||||
return NewStaticClientConfig(ips)
|
||||
}
|
||||
|
||||
func NewStaticClientConfig(ips []net.IP) (*dns.ClientConfig, error) {
|
||||
s := ``
|
||||
for _, ip := range ips {
|
||||
s += fmt.Sprintf("nameserver %s\n", ip.String())
|
||||
}
|
||||
r := strings.NewReader(s)
|
||||
return dns.ClientConfigFromReader(r)
|
||||
}
|
||||
|
||||
func NewHandler(hostnameMap map[string]net.IP) (dns.Handler, error) {
|
||||
cc, err := NewClientConfig()
|
||||
if err != nil {
|
||||
fallbackIPs := []net.IP{net.ParseIP("8.8.8.8"), net.ParseIP("1.1.1.1")}
|
||||
logrus.WithError(err).Warnf("failed to detect system DNS, falling back to %v", fallbackIPs)
|
||||
cc, err = NewStaticClientConfig(fallbackIPs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
clients := []*dns.Client{
|
||||
&dns.Client{}, // UDP
|
||||
&dns.Client{Net: "tcp"},
|
||||
}
|
||||
canonMap := make(map[string]net.IP)
|
||||
for vague, ip := range hostnameMap {
|
||||
canon := dns.CanonicalName(vague)
|
||||
canonMap[canon] = ip
|
||||
}
|
||||
h := &Handler{
|
||||
clientConfig: cc,
|
||||
clients: clients,
|
||||
canonMap: canonMap,
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
clientConfig *dns.ClientConfig
|
||||
clients []*dns.Client
|
||||
canonMap map[string]net.IP
|
||||
}
|
||||
|
||||
func (h *Handler) handleQuery(w dns.ResponseWriter, req *dns.Msg) {
|
||||
var (
|
||||
reply dns.Msg
|
||||
handled bool
|
||||
)
|
||||
reply.SetReply(req)
|
||||
for _, q := range reply.Question {
|
||||
canon := dns.CanonicalName(q.Name)
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
if ip, ok := h.canonMap[canon]; ok {
|
||||
a := &dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: q.Name,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: ip,
|
||||
}
|
||||
reply.Answer = append(reply.Answer, a)
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if handled {
|
||||
w.WriteMsg(&reply)
|
||||
return
|
||||
}
|
||||
h.handleDefault(w, req)
|
||||
}
|
||||
|
||||
func (h *Handler) handleDefault(w dns.ResponseWriter, req *dns.Msg) {
|
||||
for _, client := range h.clients {
|
||||
for _, srv := range h.clientConfig.Servers {
|
||||
addr := fmt.Sprintf("%s:%s", srv, h.clientConfig.Port)
|
||||
reply, _, err := client.Exchange(req, addr)
|
||||
if err == nil {
|
||||
w.WriteMsg(reply)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
var reply dns.Msg
|
||||
reply.SetReply(req)
|
||||
w.WriteMsg(&reply)
|
||||
}
|
||||
|
||||
func (h *Handler) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
|
||||
switch req.Opcode {
|
||||
case dns.OpcodeQuery:
|
||||
h.handleQuery(w, req)
|
||||
default:
|
||||
h.handleDefault(w, req)
|
||||
}
|
||||
}
|
||||
24
pkg/builtinports/builtinports.go
Normal file
24
pkg/builtinports/builtinports.go
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright (C) NoRouter authors.
|
||||
|
||||
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
|
||||
|
||||
http://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 builtinports
|
||||
|
||||
const (
|
||||
// DNSTCP is the default TCP port number of the built-in DNS.
|
||||
// The port number was chosen so that it can be associated with a loopback device without the root privileges.
|
||||
// Note that resolv.conf does not support specifying non-53 port.
|
||||
DNSTCP = 10053
|
||||
)
|
||||
@@ -94,6 +94,7 @@ func NewCmdClient(ctx context.Context, hostname string, pm *parsed.ParsedManifes
|
||||
configRequestArgs.StateDir.Disable = h.StateDir.Disable
|
||||
configRequestArgs.WriteEtcHosts = h.WriteEtcHosts
|
||||
configRequestArgs.Routes = pm.Routes
|
||||
configRequestArgs.NameServers = pm.NameServers
|
||||
configRequestArgsB, err := json.Marshal(configRequestArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -218,6 +218,11 @@ func (r *Manager) validateAgentFeatures(vip string, data jsonmsg.ConfigureResult
|
||||
vip, version.FeatureRoutes)
|
||||
}
|
||||
}
|
||||
if _, ok := fm[version.FeatureDNS]; !ok {
|
||||
// not a critical error
|
||||
logrus.Warnf("%s lacks feature %q, built-in DNS will be disabled",
|
||||
vip, version.FeatureDNS)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/shlex"
|
||||
"github.com/norouter/norouter/pkg/builtinports"
|
||||
"github.com/norouter/norouter/pkg/manager/manifest"
|
||||
"github.com/norouter/norouter/pkg/stream/jsonmsg"
|
||||
"github.com/pkg/errors"
|
||||
@@ -32,6 +33,7 @@ type ParsedManifest struct {
|
||||
Hosts map[string]*Host
|
||||
PublicHostPorts []*jsonmsg.IPPortProto
|
||||
Routes []jsonmsg.Route
|
||||
NameServers []jsonmsg.NameServer
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
@@ -178,6 +180,18 @@ func New(raw *manifest.Manifest) (*ParsedManifest, error) {
|
||||
}
|
||||
pm.Routes = append(pm.Routes, *route)
|
||||
}
|
||||
|
||||
// TODO: support specifying custom DNS ports via YAML
|
||||
for _, h := range pm.Hosts {
|
||||
ns := jsonmsg.NameServer{
|
||||
IPPortProto: jsonmsg.IPPortProto{
|
||||
IP: h.VIP,
|
||||
Port: builtinports.DNSTCP,
|
||||
Proto: "tcp",
|
||||
},
|
||||
}
|
||||
pm.NameServers = append(pm.NameServers, ns)
|
||||
}
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ type ConfigureRequestArgs struct {
|
||||
Loopback Loopback `json:"loopback,omitempty"`
|
||||
StateDir StateDir `json:"stateDir,omitempty"`
|
||||
WriteEtcHosts bool `json:"writeEtcHosts,omitempty"`
|
||||
Routes []Route `json:"routes,omitempty"`
|
||||
// Fields added in v0.5.0
|
||||
Routes []Route `json:"routes,omitempty"`
|
||||
NameServers []NameServer `json:"nameServers,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigureResultData struct {
|
||||
@@ -46,6 +48,7 @@ type ConfigureResultData struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// Forward uses snake_case rather than camelCase by accident :(
|
||||
type Forward struct {
|
||||
// listenIP is "me"
|
||||
ListenPort uint16 `json:"listen_port"`
|
||||
@@ -84,3 +87,8 @@ type Route struct {
|
||||
To []string `json:"to"`
|
||||
Via net.IP `json:"via"`
|
||||
}
|
||||
|
||||
// NameServer represents a built-in virtual DNS
|
||||
type NameServer struct {
|
||||
IPPortProto
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ const (
|
||||
FeatureEtcHosts = "etchosts" // Writing /etc/hosts when possible
|
||||
// Features introduced in v0.5.0:
|
||||
FeatureRoutes = "routes" // Drawing packets into a specific host. Only meaningful for HTTP and SOCKS proxy modes.
|
||||
FeatureDNS = "dns" // Built-in DNS (10053/tcp)
|
||||
// Features introduced in vX.Y.Z:
|
||||
// ...
|
||||
)
|
||||
|
||||
var Features = []Feature{FeatureLoopback, FeatureTCP, FeatureHTTP, FeatureLoopbackDisable, FeatureSOCKS, FeatureHostAliases, FeatureEtcHosts, FeatureRoutes}
|
||||
var Features = []Feature{FeatureLoopback, FeatureTCP, FeatureHTTP, FeatureLoopbackDisable, FeatureSOCKS, FeatureHostAliases, FeatureEtcHosts, FeatureRoutes, FeatureDNS}
|
||||
|
||||
Reference in New Issue
Block a user