removed
This commit is contained in:
parent
9f7ac05652
commit
984d8a7781
|
@ -1,5 +0,0 @@
|
|||
; This is a zone-signing key, keyid 21798, for miek.nl.
|
||||
; Created: 20110122104659 (Sat Jan 22 11:46:59 2011)
|
||||
; Publish: 20110122104659 (Sat Jan 22 11:46:59 2011)
|
||||
; Activate: 20110122104659 (Sat Jan 22 11:46:59 2011)
|
||||
miek.nl. IN DNSKEY 256 3 5 AwEAAQGk589980oEiFs1AUicHq2YlHbE5n0DWgfgCqTfBZ1ZYRjn2mye mQotdt4Yzq97nBPy1nzxrZhKabhX9nZriVv8jurPLXeauW/NT1Drfrjz DQRidU+A1DvLDkEKeEl8O+kOyiIHK49kUrjbfar9yQuFZUAeo+24FRfH q9oGysW70Q==
|
|
@ -1,13 +0,0 @@
|
|||
Private-key-format: v1.3
|
||||
Algorithm: 5 (RSASHA1)
|
||||
Modulus: AaTnz33zSgSIWzUBSJwerZiUdsTmfQNaB+AKpN8FnVlhGOfabJ6ZCi123hjOr3ucE/LWfPGtmEppuFf2dmuJW/yO6s8td5q5b81PUOt+uPMNBGJ1T4DUO8sOQQp4SXw76Q7KIgcrj2RSuNt9qv3JC4VlQB6j7bgVF8er2gbKxbvR
|
||||
PublicExponent: AQAB
|
||||
PrivateExponent: /IkdBCupeEi7uHS5tPnvHAHPtNm5nf4xhWm9fBYpT0wjnlB+JTYbViXgoa+4uAhwK54nPvXxzovZz+UPLfwvFBoG3D0vYS+M9WWOBCnEuDK0MfcBWfTE2hlV13xDll1o7Pj/fvpRQ7paBhjpP6uBwlVI1vH6GR9kNXQRfWK1NQU=
|
||||
Prime1: AdG+8ixEeDzHKI2GRD7lGhrQ8EzN4Tc0mek1u6ioFZ0imohaPqtqNq7RWVo35cWuvYflhFQYzFn99HGRvfGfDv8=
|
||||
Prime2: 51psvlotBXuaqzrgfb5I6u7DG9JhU5WO68PZf1RMmq2e2xLvKvDGXCP5oFur9AOsHdbmahnzgFC1s18vg7kFLw==
|
||||
Exponent1: glXRJ5oxm7CQJKrCRmeOmpqF5Lhooi5SM/UZguUmx0Z7wFSg3Q9oJhvnyVuDLYLs/y63jWEzLqvm0DFc2lUMuQ==
|
||||
Exponent2: Aq3qan3y3Yhj7y28YdhtUcM4IT9bfzNRN2vKPg5E4Nm36EOc33twYKrN/kxxfl74hFPz0TDBwC+vGwe0LitbYw==
|
||||
Coefficient: AZX3xIGzo/3fw4ouA6nAjpiWGpTK+OdFRkZtvbmzwgqnFDQopB0SweVnd1shpKCXkPTkdvpLTdmhU/84CW5m7cQ=
|
||||
Created: 20110122104659
|
||||
Publish: 20110122104659
|
||||
Activate: 20110122104659
|
|
@ -1,10 +0,0 @@
|
|||
# Copyright 2009 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
include $(GOROOT)/src/Make.inc
|
||||
TARG=funkensturm
|
||||
GOFILES=funkensturm.go\
|
||||
config_delay.go\
|
||||
|
||||
DEPS=../../
|
||||
include $(GOROOT)/src/Make.cmd
|
|
@ -1,10 +0,0 @@
|
|||
# Copyright 2009 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
include $(GOROOT)/src/Make.inc
|
||||
TARG=funkensturm
|
||||
GOFILES=funkensturm.go\
|
||||
config_rproxy.go\
|
||||
|
||||
DEPS=../../
|
||||
include $(GOROOT)/src/Make.cmd
|
|
@ -1,10 +0,0 @@
|
|||
# Copyright 2009 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
include $(GOROOT)/src/Make.inc
|
||||
TARG=funkensturm
|
||||
GOFILES=funkensturm.go\
|
||||
config_sign.go\
|
||||
|
||||
DEPS=../../
|
||||
include $(GOROOT)/src/Make.cmd
|
|
@ -1,44 +0,0 @@
|
|||
package main
|
||||
|
||||
// This is transparant proxy
|
||||
|
||||
import (
|
||||
"dns"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func send(m *dns.Msg) (buf []byte) {
|
||||
if *verbose {
|
||||
fmt.Printf("--> %s\n", m.Question[0].String())
|
||||
}
|
||||
|
||||
var o *dns.Msg
|
||||
var err error
|
||||
for _, c := range qr {
|
||||
o, err = c.Client.Exchange(m, c.Addr)
|
||||
if *verbose {
|
||||
if err == nil {
|
||||
fmt.Printf("<-- %s\n", m.Question[0].String())
|
||||
} else {
|
||||
fmt.Printf("%s\n", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
buf, _ = o.Pack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Return the configration
|
||||
func NewFunkenSturm() *FunkenSturm {
|
||||
f := new(FunkenSturm)
|
||||
f.Setup = func() bool { return true } // no setup
|
||||
f.Default = send
|
||||
|
||||
f.Funk = make([]*Funk, 1) // 1 Funk chain
|
||||
f.Funk[0] = new(Funk)
|
||||
f.Funk[0].Match = func(m *dns.Msg) (*dns.Msg, bool) { return m, true }
|
||||
f.Funk[0].Action = send
|
||||
return f
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package main
|
||||
|
||||
// This proxy delays pkt that have the RD bit set.
|
||||
|
||||
import (
|
||||
"dns"
|
||||
"time"
|
||||
)
|
||||
|
||||
const NSECDELAY = 1 * 1e9 // 1 second, meaning 1 qps (smaller means higher qps)
|
||||
|
||||
var previous int64 // previous tick
|
||||
|
||||
// returns false if we hit the limit set by NSECDELAY
|
||||
func checkDelay() (ti int64, limitok bool) {
|
||||
current := time.Nanoseconds()
|
||||
tdiff := (current - previous)
|
||||
if tdiff < NSECDELAY {
|
||||
// too often
|
||||
return previous, false
|
||||
}
|
||||
return current, true
|
||||
}
|
||||
|
||||
// the only matching we do is on the RD bit
|
||||
func match(m *dns.Msg) (*dns.Msg, bool) {
|
||||
// only delay pkts with RD bit
|
||||
return m, m.MsgHdr.RecursionDesired == true
|
||||
}
|
||||
|
||||
func delay(m *dns.Msg) (buf []byte) {
|
||||
var (
|
||||
ok1 bool
|
||||
o *dns.Msg
|
||||
)
|
||||
if previous, ok1 = checkDelay(); !ok1 {
|
||||
println("Dropping: too often")
|
||||
time.Sleep(NSECDELAY)
|
||||
return
|
||||
}
|
||||
println("Ok: let it through")
|
||||
for _, c := range qr {
|
||||
o, _ = c.Client.Exchange(m, c.Addr)
|
||||
}
|
||||
buf, _ = o.Pack()
|
||||
return
|
||||
}
|
||||
|
||||
// Return the configration
|
||||
func NewFunkenSturm() *FunkenSturm {
|
||||
f := new(FunkenSturm)
|
||||
f.Setup = func() bool { previous = time.Nanoseconds(); return true }
|
||||
|
||||
f.Funk = make([]*Funk, 1)
|
||||
f.Funk[0] = new(Funk)
|
||||
f.Funk[0].Match = match
|
||||
f.Funk[0].Action = delay
|
||||
return f
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package main
|
||||
|
||||
// Keep a local cache of DNS packets. Match incoming
|
||||
// qname,qclass,qtype and return the saved packet.
|
||||
// On a cache miss consult the nameserver
|
||||
|
||||
import (
|
||||
"dns"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Keep everything in the cache for 60 seconds
|
||||
const (
|
||||
CACHETTL = 1
|
||||
_CLASS = 2 << 16
|
||||
|
||||
INSERT = iota
|
||||
DELETE
|
||||
)
|
||||
|
||||
var cache Cache
|
||||
|
||||
type item struct {
|
||||
epoch int64
|
||||
msg []byte
|
||||
}
|
||||
|
||||
// Number in the second map denotes the class + type.
|
||||
func intval(c, t uint16) int {
|
||||
return int(c)*_CLASS + int(t)
|
||||
}
|
||||
|
||||
// Mutex entry in the cache, if non-nill take the lock
|
||||
// Ala Zone in zone.go, but slightly different
|
||||
type Cache struct {
|
||||
data map[string]map[int]*item
|
||||
rw *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCache() Cache {
|
||||
c := new(Cache)
|
||||
c.data = make(map[string]map[int]*item)
|
||||
c.rw = new(sync.RWMutex)
|
||||
return *c
|
||||
}
|
||||
|
||||
// Add an entry to the cache. The old entry (if any) gets overwritten
|
||||
func (c Cache) add(q *dns.Msg) {
|
||||
c.rw.Lock()
|
||||
defer c.rw.Unlock()
|
||||
qname := q.Question[0].Name
|
||||
i := intval(q.Question[0].Qclass, q.Question[0].Qtype)
|
||||
if c.data[qname] == nil {
|
||||
im := make(map[int]*item)
|
||||
c.data[qname] = im
|
||||
}
|
||||
buf, _ := q.Pack()
|
||||
im := c.data[qname]
|
||||
im[i] = &item{time.Seconds(), buf}
|
||||
}
|
||||
|
||||
// Lookup an entry in the cache. Returns nil
|
||||
// when nothing found.
|
||||
func (c Cache) lookup(q *dns.Msg) []byte {
|
||||
// Use the question section for looking up
|
||||
c.rw.RLock()
|
||||
defer c.rw.RUnlock()
|
||||
i := intval(q.Question[0].Qclass, q.Question[0].Qtype)
|
||||
if im, ok := c.data[q.Question[0].Name]; ok {
|
||||
// we have the name
|
||||
if d, ok := im[i]; ok {
|
||||
// We even have the entry, check cache time
|
||||
if time.Seconds()-d.epoch > CACHETTL {
|
||||
// Too olds means we get a new one
|
||||
return nil
|
||||
}
|
||||
e := make([]byte, len(d.msg))
|
||||
copy(e, d.msg)
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkcache(m *dns.Msg) (o []byte) {
|
||||
// Check if we have the packet in Cache
|
||||
// if so, return it. Otherwise ask the
|
||||
// server, return that answer and put it
|
||||
// in the cache.
|
||||
o = cache.lookup(m)
|
||||
if o != nil {
|
||||
// octet 1 and 2 contain the Id, set the one for the current pkt
|
||||
dns.RawSetId(o, 0, m.MsgHdr.Id)
|
||||
return
|
||||
}
|
||||
|
||||
println("Cache miss")
|
||||
var p *dns.Msg
|
||||
for _, c := range qr {
|
||||
p, _ = c.Client.Exchange(m, c.Addr)
|
||||
}
|
||||
cache.add(p)
|
||||
o, _ = p.Pack()
|
||||
return
|
||||
}
|
||||
|
||||
// Return the configration
|
||||
func NewFunkenSturm() *FunkenSturm {
|
||||
f := new(FunkenSturm)
|
||||
f.Funk = make([]*Funk, 1)
|
||||
f.Setup = func() bool { cache = NewCache(); return true }
|
||||
f.Funk[0] = new(Funk)
|
||||
f.Funk[0].Match = func(m *dns.Msg) (*dns.Msg, bool) { return m, true }
|
||||
f.Funk[0].Action = checkcache
|
||||
return f
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package main
|
||||
|
||||
// This is a signing proxy.
|
||||
|
||||
// Lots of hardcoded stuff. The first record in the answer section is
|
||||
// signed with the key for example.org. The RRSIG is added to the packet.
|
||||
// We could also use one 1 key for multiple domains.
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"dns"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func sign(m *dns.Msg) *dns.Msg {
|
||||
sg := new(dns.RR_RRSIG)
|
||||
sg.Hdr = dns.RR_Header{"c.miek.nl.", dns.TypeRRSIG, dns.ClassINET, 14400, 0}
|
||||
sg.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05"
|
||||
sg.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05"
|
||||
sg.KeyTag = pubkey.KeyTag() // Get the keyfrom the Key
|
||||
sg.SignerName = pubkey.Hdr.Name
|
||||
sg.Algorithm = dns.RSASHA256
|
||||
|
||||
if len(m.Answer) > 0 {
|
||||
// sign the first record
|
||||
an := m.Answer[0]
|
||||
sg.TypeCovered = an.Header().Rrtype
|
||||
sg.Labels = dns.Labels(an.Header().Name)
|
||||
sg.OrigTtl = an.Header().Ttl
|
||||
switch p := privkey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
sg.Sign(p, []dns.RR{an})
|
||||
}
|
||||
}
|
||||
m.Answer = append(m.Answer, sg)
|
||||
return m
|
||||
}
|
||||
|
||||
func sendsign(m *dns.Msg) (o []byte) {
|
||||
var p *dns.Msg
|
||||
for _, c := range qr {
|
||||
p, _ = c.Client.Exchange(m, c.Addr)
|
||||
}
|
||||
o, _ = sign(p).Pack()
|
||||
println("signing")
|
||||
return
|
||||
}
|
||||
|
||||
func send(m *dns.Msg) (o []byte) {
|
||||
var p *dns.Msg
|
||||
for _, c := range qr {
|
||||
p, _ = c.Client.Exchange(m, c.Addr)
|
||||
}
|
||||
o, _ = p.Pack()
|
||||
return
|
||||
}
|
||||
|
||||
var pubkey *dns.RR_DNSKEY
|
||||
var privkey dns.PrivateKey
|
||||
|
||||
func setup() bool {
|
||||
privdata := `Private-key-format: v1.3
|
||||
Algorithm: 5 (RSASHA1)
|
||||
Modulus: AaTnz33zSgSIWzUBSJwerZiUdsTmfQNaB+AKpN8FnVlhGOfabJ6ZCi123hjOr3ucE/LWfPGtmEppuFf2dmuJW/yO6s8td5q5b81PUOt+uPMNBGJ1T4DUO8sOQQp4SXw76Q7KIgcrj2RSuNt9qv3JC4VlQB6j7bgVF8er2gbKxbvR
|
||||
PublicExponent: AQAB
|
||||
PrivateExponent: /IkdBCupeEi7uHS5tPnvHAHPtNm5nf4xhWm9fBYpT0wjnlB+JTYbViXgoa+4uAhwK54nPvXxzovZz+UPLfwvFBoG3D0vYS+M9WWOBCnEuDK0MfcBWfTE2hlV13xDll1o7Pj/fvpRQ7paBhjpP6uBwlVI1vH6GR9kNXQRfWK1NQU=
|
||||
Prime1: AdG+8ixEeDzHKI2GRD7lGhrQ8EzN4Tc0mek1u6ioFZ0imohaPqtqNq7RWVo35cWuvYflhFQYzFn99HGRvfGfDv8=
|
||||
Prime2: 51psvlotBXuaqzrgfb5I6u7DG9JhU5WO68PZf1RMmq2e2xLvKvDGXCP5oFur9AOsHdbmahnzgFC1s18vg7kFLw==
|
||||
Exponent1: glXRJ5oxm7CQJKrCRmeOmpqF5Lhooi5SM/UZguUmx0Z7wFSg3Q9oJhvnyVuDLYLs/y63jWEzLqvm0DFc2lUMuQ==
|
||||
Exponent2: Aq3qan3y3Yhj7y28YdhtUcM4IT9bfzNRN2vKPg5E4Nm36EOc33twYKrN/kxxfl74hFPz0TDBwC+vGwe0LitbYw==
|
||||
Coefficient: AZX3xIGzo/3fw4ouA6nAjpiWGpTK+OdFRkZtvbmzwgqnFDQopB0SweVnd1shpKCXkPTkdvpLTdmhU/84CW5m7cQ=
|
||||
Created: 20110122104659
|
||||
Publish: 20110122104659
|
||||
Activate: 20110122104659`
|
||||
pubkey = new(dns.RR_DNSKEY)
|
||||
privkey, _ = pubkey.ReadPrivateKey(strings.NewReader(privdata))
|
||||
pubkey.Hdr = dns.RR_Header{"miek.nl.", dns.TypeDNSKEY, dns.ClassINET, 3600, 0}
|
||||
pubkey.Protocol = 3
|
||||
pubkey.Flags = 256
|
||||
return true
|
||||
}
|
||||
|
||||
// Return the configration
|
||||
func NewFunkenSturm() *FunkenSturm {
|
||||
f := new(FunkenSturm)
|
||||
f.Funk = make([]*Funk, 1)
|
||||
f.Setup = setup
|
||||
f.Default = send
|
||||
|
||||
f.Funk[0] = new(Funk)
|
||||
f.Funk[0].Match = func(m *dns.Msg) (*dns.Msg, bool) { return m, m.Question[0].Name == "c.miek.nl." }
|
||||
f.Funk[0].Action = sendsign
|
||||
return f
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright 2011 Miek Gieben. All rights reserved.
|
||||
// Lisenced under the GPLv2
|
||||
|
||||
/*
|
||||
Funkensturm rewrites DNS packets in the broadest sense of the word.
|
||||
The rewriting can include delayed (re)sending of packets, (re)sending
|
||||
packets to multiple servers, rewriting the packet contents, for instance
|
||||
by signing a packet, or the other way around, stripping the signatures.
|
||||
|
||||
In its essence this is no different that a recursive nameserver, which also
|
||||
receives and sends queries. The difference is the huge amount of tweaking
|
||||
Funkensturm offers.
|
||||
|
||||
The configuration of Funkensturm is done by writing it in Go - a
|
||||
separate configuration language was deemed to be unpractical and
|
||||
would limit the possibilities.
|
||||
|
||||
Usage:
|
||||
funkensturm [flags]
|
||||
|
||||
The flags are:
|
||||
|
||||
-sserver
|
||||
Listener address and port for the server. This has to be
|
||||
specified as: address:port, for instance 127.0.0.1:8053.
|
||||
This is also the default.
|
||||
-rserver
|
||||
Remote server address in address:port format. This can be
|
||||
repeated, for each rserver a resolver channel is created.
|
||||
The first begin `qr[0]`, the second `qr[1]`, etc.
|
||||
The default is: 127.0.0.1:53
|
||||
|
||||
Debugging flags:
|
||||
|
||||
-verbose
|
||||
Print packets as they flow through Funkensturm.
|
||||
|
||||
Predefined configurations are shown in `config_delay.go` and `config_sign.go`. The
|
||||
default `config.go` implements a transparant proxy.
|
||||
|
||||
Also see: http://www.miek.nl/blog/archives/2011/01/23/funkensturm/index.html for
|
||||
a architectural overview.
|
||||
|
||||
In FunkenSturm you define chains named Funk's (maybe just 'chain' is a better name). Each Funk
|
||||
consists out of match and action function. If the match function matches (return true) the
|
||||
action function is called.
|
||||
Multiple Funk's may be used. The first 'true' value win and that action function is performed.
|
||||
|
||||
*/
|
||||
package documentation
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
* Funkensturm, a versatile DNS proxy
|
||||
* Miek Gieben <miek@miek.nl> (c) 2011
|
||||
* GPLv2
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"dns"
|
||||
"flag"
|
||||
"os/signal"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var qr []*FunkClient
|
||||
var f *FunkenSturm
|
||||
var verbose *bool
|
||||
|
||||
// A small wrapper to keep the address together
|
||||
// with a client.
|
||||
type FunkClient struct {
|
||||
Client *dns.Client
|
||||
Addr string
|
||||
}
|
||||
|
||||
// A FunkAction combines a set of matches and an action. If
|
||||
// the matches are successfull (return true) the action is
|
||||
// performed
|
||||
type Funk struct {
|
||||
Match func(*dns.Msg) (*dns.Msg, bool)
|
||||
Action func(*dns.Msg) []byte
|
||||
}
|
||||
|
||||
// Hold the information.
|
||||
type FunkenSturm struct {
|
||||
Setup func() bool // Inital setup (for extra resolvers, or loading keys, or ...)
|
||||
Default func(*dns.Msg) []byte // Default action is all fails
|
||||
Funk []*Funk // The configuration
|
||||
}
|
||||
|
||||
func doFunkenSturm(pkt *dns.Msg) (ret []byte) {
|
||||
// No matter what, we refuse to answer requests with the response bit set.
|
||||
if pkt.MsgHdr.Response == true {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop through the Funks and decide what to do with the packet.
|
||||
for _, f := range f.Funk {
|
||||
if m, ok := f.Match(pkt); ok {
|
||||
ret = f.Action(m)
|
||||
return
|
||||
}
|
||||
}
|
||||
if f.Default == nil {
|
||||
println("No f.Default set!")
|
||||
return
|
||||
}
|
||||
ret = f.Default(pkt)
|
||||
return
|
||||
}
|
||||
|
||||
func serve(w dns.ResponseWriter, req *dns.Msg) {
|
||||
if out := doFunkenSturm(req); out != nil {
|
||||
w.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
func listenAndServe(add, net string) {
|
||||
if err := dns.ListenAndServe(add, net, nil); err != nil {
|
||||
println("Failed to setup:", net, add)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
listen := flag.String("listen", "127.0.0.1:8053", "set the listener address")
|
||||
server := flag.String("server", "127.0.0.1:53", "remote server address(es), seperate with commas")
|
||||
verbose = flag.Bool("verbose", false, "Print packet as it flows through")
|
||||
// cpuprofile := flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
flag.Usage = func() {
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
/*
|
||||
if *cpuprofile != "" {
|
||||
f, err := os.Create(*cpuprofile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
*/
|
||||
|
||||
clients := strings.Split(*server, ",")
|
||||
qr = make([]*FunkClient, len(clients))
|
||||
for i, ra := range clients {
|
||||
qr[i] = new(FunkClient)
|
||||
qr[i].Client = dns.NewClient()
|
||||
qr[i].Addr = ra
|
||||
}
|
||||
|
||||
f = NewFunkenSturm()
|
||||
ok := f.Setup()
|
||||
if !ok {
|
||||
println("Setup failed")
|
||||
return
|
||||
}
|
||||
|
||||
dns.HandleFunc(".", serve)
|
||||
go listenAndServe(*listen, "tcp")
|
||||
go listenAndServe(*listen, "udp")
|
||||
|
||||
forever:
|
||||
for {
|
||||
select {
|
||||
case <-signal.Incoming:
|
||||
println("Signal received, stopping")
|
||||
break forever
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue