Skip to content

Commit 9c24615

Browse files
committed
add tcp sockets, update docs
1 parent 220178c commit 9c24615

File tree

6 files changed

+85
-41
lines changed

6 files changed

+85
-41
lines changed

README.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This daemon was created to solve the problem of manipulating traffic based on do
1111
* Client sends DNS request to a recursive DNS server which supports DNSTap (**unbound**, **bind** etc)
1212
* DNS server logs the reply information through DNSTap to **dnstap-bgp**
1313
* **dnstap-bgp** caches the IPs from the reply and announces them through BGP
14-
* When the request for the already cached IP comes again - refresh it's TTL
14+
* When the request for the already cached IP comes again - refresh its TTL
1515

1616
## Features
1717
* Load a list of domains to intercept: the prefix tree is used to match subdomains
@@ -21,11 +21,11 @@ This daemon was created to solve the problem of manipulating traffic based on do
2121
* Export routes to any number of BGP peers
2222
* Configurable timeout to purge entries from the cache
2323
* Persist the cache on disk (in a Bolt database)
24-
* Sync the obtained IPs with other instances of **dnstap-bgp** using simple HTTP requests
24+
* Sync the obtained IPs with other instances of **dnstap-bgp**
2525
* Can switch itself to a pre-created network namespace before initializing network. This can be useful if you want to peer with a BGP server running on the same host (e.g. **bird** does not support peering with any of the local interfaces). This requires running as *root*.
2626

2727
## Synchronization
28-
**dnstap-bgp** can optionally push the obtained IPs to other **dnstap-bgp** instances. Also it periodically syncs its cache with peers to keep it up-to-date in case of network outages. The interaction is done using simple HTTP queries and JSON.
28+
**dnstap-bgp** can optionally push the obtained IPs to other **dnstap-bgp** instances. It also periodically syncs its cache with peers to keep it up-to-date in case of network outages. The interaction is done using simple HTTP queries and JSON.
2929

3030
## Limitations
3131
* IDN (punycode) domain names are currenly not supported and are silently skipped
@@ -38,17 +38,28 @@ This daemon was created to solve the problem of manipulating traffic based on do
3838
See *deploy/dnstap-bgp.toml* for an example configuration and description of parameters.
3939

4040
## Examples
41-
### unbound.conf
42-
```
43-
...
41+
DNSTap works in a client-server manner, where DNS server is *the client** and **dnstap-bgp** is a server.
42+
43+
### Unbound
44+
Unbound seem to be able to work with DNSTap only through UNIX sockets.
4445

46+
```
4547
dnstap:
4648
dnstap-enable: yes
4749
dnstap-socket-path: "/tmp/dnstap.sock"
48-
dnstap-send-identity: no
49-
dnstap-send-version: no
50-
5150
dnstap-log-client-response-messages: yes
5251
```
5352

5453
**Important** In Ubuntu access to the DNSTap socket for Unbound is blocked by default by AppArmor rules. Either disable it for the Unbound binary or fix the rules.
54+
55+
### BIND
56+
DNSTap is supported since 9.11, but usually is not built-in, at least in Ubuntu packages.
57+
BIND also can connect only using UNIX socket.
58+
59+
```
60+
dnstap {
61+
client response;
62+
};
63+
64+
dnstap-output unix "/tmp/dnstap.sock"
65+
```

deploy/dnstap-bgp.toml

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
# Path to a list of domains to match
1+
# Path to a list of domains to match - one domain per line
2+
# If a higher-level domain exists in the list - its subdomains will not be loaded, but still matched
23
# Currently IDN domains are not supported
3-
domains = "/var/cache/rkn_domains.txt"
4+
domains = "/var/cache/domains.txt"
45

56
# Path to a BoltDB file where to persist the cache
67
# Optional
78
cache = "/var/cache/dnstap-bgp.db"
89

910
# TTL of the entries in cache
10-
# If the entry is not requested by clients for this duration the it's purged from the cache
11+
# If the entry is not requested by clients for this period then it's purged from the cache
12+
# Optional, default 24h
1113
ttl = "24h"
1214

1315
[dnstap]
14-
# Path to a DNSTap socket
15-
socket = "/tmp/dnstap.sock"
16+
# IP:Port or a path to a UNIX socket file to listen on
17+
# listen = "0.0.0.0:1234"
18+
listen = "/tmp/dnstap.sock"
1619

17-
# Permissions which are set on the socket file
20+
# Permissions which are set on the socket file if listening on UNIX socket
21+
# Optional, has no effect if using TCP
1822
perm = "0666"
1923

2024
[bgp]
@@ -32,17 +36,24 @@ sourceIP = "192.168.113.1"
3236
# It it's also not defined - then RouterID
3337
nextHop = "192.168.112.1"
3438

35-
# List of BGP peers
39+
# List of BGP peers in hostname or hostname:port formats
3640
peers = [
3741
"192.168.0.1",
3842
"192.168.0.2:177",
3943
]
4044

4145
[syncer]
42-
# Where to listen for sync requests
46+
# Where to listen for the sync requests
47+
# Optional, if not set - no incoming syncs will be allowed
4348
listen = "0.0.0.0:8080"
4449

45-
# Sync peers
50+
# How frequently to perform full sync with peers
51+
# Optional, default 10m
52+
# If set to zero - no periodic syncs performed
53+
syncInterval = "10m"
54+
55+
# Peers to sync with in hostname:port format
56+
# Optional, if not specified - no sync or push performed
4657
peers = [
4758
"192.168.0.2:8080",
4859
]

dnstap.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
)
1515

1616
type dnstapCfg struct {
17-
Socket string
17+
Listen string
1818
Perm string
1919
}
2020

@@ -25,6 +25,8 @@ type dnstapServer struct {
2525
cb fCb
2626
cbErr fCbErr
2727
fstrmServer *dnstap.FrameStreamSockInput
28+
l net.Listener
29+
unixSocket bool
2830
ch chan []byte
2931
}
3032

@@ -101,22 +103,30 @@ func newDnstapServer(c *dnstapCfg, cb fCb, cbErr fCbErr) (ds *dnstapServer, err
101103
cbErr: cbErr,
102104
}
103105

104-
if c.Socket == "" {
105-
log.Fatal("You need to specify DNSTap socket")
106+
if c.Listen == "" {
107+
return nil, fmt.Errorf("You need to specify DNSTap listening poing")
106108
}
107109

108-
ds.fstrmServer, err = dnstap.NewFrameStreamSockInputFromPath(c.Socket)
109-
if err != nil {
110-
return nil, fmt.Errorf("DNSTap listening error: %s", err)
111-
}
110+
if addr, err := net.ResolveTCPAddr("tcp", c.Listen); err == nil {
111+
if ds.l, err = net.ListenTCP("tcp", addr); err != nil {
112+
return nil, fmt.Errorf("Unable to listen on '%s': %s", c.Listen, err)
113+
}
112114

113-
if c.Perm != "" {
114-
octal, err := strconv.ParseInt(c.Perm, 8, 32)
115+
ds.fstrmServer = dnstap.NewFrameStreamSockInput(ds.l)
116+
} else {
117+
ds.fstrmServer, err = dnstap.NewFrameStreamSockInputFromPath(c.Listen)
115118
if err != nil {
116-
return nil, fmt.Errorf("Unable to parse '%s' as octal: %s", c.Perm, err)
119+
return nil, fmt.Errorf("Unable to listen on '%s': %s", c.Listen, err)
117120
}
118121

119-
os.Chmod(c.Socket, os.FileMode(octal))
122+
if c.Perm != "" {
123+
octal, err := strconv.ParseInt(c.Perm, 8, 32)
124+
if err != nil {
125+
return nil, fmt.Errorf("Unable to parse '%s' as octal: %s", c.Perm, err)
126+
}
127+
128+
os.Chmod(c.Listen, os.FileMode(octal))
129+
}
120130
}
121131

122132
for i := 0; i < runtime.NumCPU(); i++ {

dnstap_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func Test_DNSTap(t *testing.T) {
3030
}
3131

3232
_, err := newDnstapServer(&dnstapCfg{
33-
Socket: "dnstap.sock",
33+
Listen: "dnstap.sock",
3434
Perm: "666",
3535
}, cb, cbe)
3636
assert.Nil(t, err)

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func main() {
193193
log.Fatalf("Unable to init DNSTap: %s", err)
194194
}
195195

196-
log.Printf("Created DNSTap socket %s", cfg.DNSTap.Socket)
196+
log.Printf("Listening for DNSTap on: %s", cfg.DNSTap.Listen)
197197

198198
go func() {
199199
sigchannel := make(chan os.Signal, 1)

syncer.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import (
1313
)
1414

1515
type syncerCfg struct {
16-
Listen string
17-
Peers []string
16+
Listen string
17+
SyncInterval string
18+
Peers []string
1819
}
1920

2021
type getAllFunc func() []*cacheEntry
@@ -25,7 +26,8 @@ type syncer struct {
2526
s *http.Server
2627
c *http.Client
2728

28-
peers []string
29+
syncInterval time.Duration
30+
peers []string
2931

3032
getAll getAllFunc
3133
add addFunc
@@ -36,11 +38,18 @@ type syncer struct {
3638

3739
func newSyncer(cf *syncerCfg, getAll getAllFunc, add addFunc, syncCb syncFunc) (s *syncer, err error) {
3840
s = &syncer{
39-
getAll: getAll,
40-
add: add,
41-
peers: cf.Peers,
42-
syncCb: syncCb,
43-
shutdown: make(chan struct{}),
41+
getAll: getAll,
42+
add: add,
43+
peers: cf.Peers,
44+
syncCb: syncCb,
45+
shutdown: make(chan struct{}),
46+
syncInterval: 10 * time.Minute,
47+
}
48+
49+
if cf.SyncInterval != "" {
50+
if s.syncInterval, err = time.ParseDuration(cf.SyncInterval); err != nil {
51+
return nil, fmt.Errorf("Unable to parse syncInterval: %s", err)
52+
}
4453
}
4554

4655
if len(cf.Peers) > 0 {
@@ -49,7 +58,9 @@ func newSyncer(cf *syncerCfg, getAll getAllFunc, add addFunc, syncCb syncFunc) (
4958
}
5059
}
5160

52-
go s.syncScheduler()
61+
if s.syncInterval > 0 {
62+
go s.syncScheduler()
63+
}
5364

5465
if cf.Listen == "" {
5566
return
@@ -133,12 +144,13 @@ func (s *syncer) callPeer(p, handler, method string, body io.ReadCloser) (resp *
133144
}
134145

135146
func (s *syncer) syncScheduler() {
136-
t := time.NewTicker(time.Minute)
147+
t := time.NewTicker(s.syncInterval)
137148

138149
for {
139150
select {
140151
case <-t.C:
141152
s.syncAll()
153+
142154
case <-s.shutdown:
143155
return
144156
}

0 commit comments

Comments
 (0)