Skip to content

Commit 4b6b13a

Browse files
author
Santhosh Manohar
committed
Add support for SRV query in embedded DNS
Signed-off-by: Santhosh Manohar <santhosh@docker.com>
1 parent 6c736ff commit 4b6b13a

6 files changed

Lines changed: 216 additions & 3 deletions

File tree

libnetwork_internal_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,106 @@ func TestAuxAddresses(t *testing.T) {
318318
}
319319
}
320320

321+
func TestSRVServiceQuery(t *testing.T) {
322+
c, err := New()
323+
if err != nil {
324+
t.Fatal(err)
325+
}
326+
defer c.Stop()
327+
328+
n, err := c.NewNetwork("bridge", "net1", "", nil)
329+
if err != nil {
330+
t.Fatal(err)
331+
}
332+
defer func() {
333+
if err := n.Delete(); err != nil {
334+
t.Fatal(err)
335+
}
336+
}()
337+
338+
ep, err := n.CreateEndpoint("testep")
339+
if err != nil {
340+
t.Fatal(err)
341+
}
342+
343+
sb, err := c.NewSandbox("c1")
344+
if err != nil {
345+
t.Fatal(err)
346+
}
347+
defer func() {
348+
if err := sb.Delete(); err != nil {
349+
t.Fatal(err)
350+
}
351+
}()
352+
353+
err = ep.Join(sb)
354+
if err != nil {
355+
t.Fatal(err)
356+
}
357+
358+
sr := svcInfo{
359+
svcMap: make(map[string][]net.IP),
360+
svcIPv6Map: make(map[string][]net.IP),
361+
ipMap: make(map[string]string),
362+
service: make(map[string][]servicePorts),
363+
}
364+
// backing container for the service
365+
cTarget := serviceTarget{
366+
name: "task1.web.swarm",
367+
ip: net.ParseIP("192.168.10.2"),
368+
port: 80,
369+
}
370+
// backing host for the service
371+
hTarget := serviceTarget{
372+
name: "node1.docker-cluster",
373+
ip: net.ParseIP("10.10.10.2"),
374+
port: 45321,
375+
}
376+
httpPort := servicePorts{
377+
portName: "_http",
378+
proto: "_tcp",
379+
target: []serviceTarget{cTarget},
380+
}
381+
382+
extHTTPPort := servicePorts{
383+
portName: "_host_http",
384+
proto: "_tcp",
385+
target: []serviceTarget{hTarget},
386+
}
387+
sr.service["web.swarm"] = append(sr.service["web.swarm"], httpPort)
388+
sr.service["web.swarm"] = append(sr.service["web.swarm"], extHTTPPort)
389+
390+
c.(*controller).svcRecords[n.ID()] = sr
391+
392+
_, ip, err := ep.Info().Sandbox().ResolveService("_http._tcp.web.swarm")
393+
if err != nil {
394+
t.Fatal(err)
395+
}
396+
if len(ip) == 0 {
397+
t.Fatal(err)
398+
}
399+
if ip[0].String() != "192.168.10.2" {
400+
t.Fatal(err)
401+
}
402+
403+
_, ip, err = ep.Info().Sandbox().ResolveService("_host_http._tcp.web.swarm")
404+
if err != nil {
405+
t.Fatal(err)
406+
}
407+
if len(ip) == 0 {
408+
t.Fatal(err)
409+
}
410+
if ip[0].String() != "10.10.10.2" {
411+
t.Fatal(err)
412+
}
413+
414+
// Try resolving a service name with invalid protocol, should fail..
415+
_, _, err = ep.Info().Sandbox().ResolveService("_http._icmp.web.swarm")
416+
if err == nil {
417+
t.Fatal(err)
418+
}
419+
}
420+
321421
func TestIpamReleaseOnNetDriverFailures(t *testing.T) {
322422
if !testutils.IsRunningInContainer() {
323423
defer testutils.SetupTestOSContext(t)()

libnetwork_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,10 @@ func (f *fakeSandbox) ResolveIP(ip string) string {
12241224
return ""
12251225
}
12261226

1227+
func (f *fakeSandbox) ResolveService(name string) ([]*net.SRV, []net.IP, error) {
1228+
return nil, nil, nil
1229+
}
1230+
12271231
func (f *fakeSandbox) Endpoints() []libnetwork.Endpoint {
12281232
return nil
12291233
}

network.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ type svcInfo struct {
7474
svcMap map[string][]net.IP
7575
svcIPv6Map map[string][]net.IP
7676
ipMap map[string]string
77+
service map[string][]servicePorts
7778
}
7879

7980
// IpamConf contains all the ipam related configurations for a network

resolver.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,15 +275,48 @@ func (r *resolver) handlePTRQuery(ptr string, query *dns.Msg) (*dns.Msg, error)
275275
return resp, nil
276276
}
277277

278+
func (r *resolver) handleSRVQuery(svc string, query *dns.Msg) (*dns.Msg, error) {
279+
srv, ip, err := r.sb.ResolveService(svc)
280+
281+
if err != nil {
282+
return nil, err
283+
}
284+
if len(srv) != len(ip) {
285+
return nil, fmt.Errorf("invalid reply for SRV query %s", svc)
286+
}
287+
288+
resp := createRespMsg(query)
289+
290+
for i, r := range srv {
291+
rr := new(dns.SRV)
292+
rr.Hdr = dns.RR_Header{Name: svc, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: respTTL}
293+
rr.Port = r.Port
294+
rr.Target = r.Target
295+
resp.Answer = append(resp.Answer, rr)
296+
297+
rr1 := new(dns.A)
298+
rr1.Hdr = dns.RR_Header{Name: r.Target, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: respTTL}
299+
rr1.A = ip[i]
300+
resp.Extra = append(resp.Extra, rr1)
301+
}
302+
return resp, nil
303+
304+
}
305+
278306
func truncateResp(resp *dns.Msg, maxSize int, isTCP bool) {
279307
if !isTCP {
280308
resp.Truncated = true
281309
}
282310

311+
srv := resp.Question[0].Qtype == dns.TypeSRV
283312
// trim the Answer RRs one by one till the whole message fits
284313
// within the reply size
285314
for resp.Len() > maxSize {
286315
resp.Answer = resp.Answer[:len(resp.Answer)-1]
316+
317+
if srv && len(resp.Extra) > 0 {
318+
resp.Extra = resp.Extra[:len(resp.Extra)-1]
319+
}
287320
}
288321
}
289322

@@ -299,12 +332,16 @@ func (r *resolver) ServeDNS(w dns.ResponseWriter, query *dns.Msg) {
299332
return
300333
}
301334
name := query.Question[0].Name
302-
if query.Question[0].Qtype == dns.TypeA {
335+
336+
switch query.Question[0].Qtype {
337+
case dns.TypeA:
303338
resp, err = r.handleIPQuery(name, query, types.IPv4)
304-
} else if query.Question[0].Qtype == dns.TypeAAAA {
339+
case dns.TypeAAAA:
305340
resp, err = r.handleIPQuery(name, query, types.IPv6)
306-
} else if query.Question[0].Qtype == dns.TypePTR {
341+
case dns.TypePTR:
307342
resp, err = r.handlePTRQuery(name, query)
343+
case dns.TypeSRV:
344+
resp, err = r.handleSRVQuery(name, query)
308345
}
309346

310347
if err != nil {

sandbox.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ type Sandbox interface {
4545
// ResolveIP returns the service name for the passed in IP. IP is in reverse dotted
4646
// notation; the format used for DNS PTR records
4747
ResolveIP(name string) string
48+
// ResolveService returns all the backend details about the containers or hosts
49+
// backing a service. Its purpose is to satisfy an SRV query
50+
ResolveService(name string) ([]*net.SRV, []net.IP, error)
4851
// Endpoints returns all the endpoints connected to the sandbox
4952
Endpoints() []Endpoint
5053
}
@@ -425,6 +428,61 @@ func (sb *sandbox) execFunc(f func()) {
425428
sb.osSbox.InvokeFunc(f)
426429
}
427430

431+
func (sb *sandbox) ResolveService(name string) ([]*net.SRV, []net.IP, error) {
432+
srv := []*net.SRV{}
433+
ip := []net.IP{}
434+
435+
log.Debugf("Service name To resolve: %v", name)
436+
437+
parts := strings.Split(name, ".")
438+
if len(parts) < 3 {
439+
return nil, nil, fmt.Errorf("invalid service name, %s", name)
440+
}
441+
442+
portName := parts[0]
443+
proto := parts[1]
444+
if proto != "_tcp" && proto != "_udp" {
445+
return nil, nil, fmt.Errorf("invalid protocol in service, %s", name)
446+
}
447+
svcName := strings.Join(parts[2:], ".")
448+
449+
for _, ep := range sb.getConnectedEndpoints() {
450+
n := ep.getNetwork()
451+
452+
sr, ok := n.getController().svcRecords[n.ID()]
453+
if !ok {
454+
continue
455+
}
456+
457+
svcs, ok := sr.service[svcName]
458+
if !ok {
459+
continue
460+
}
461+
462+
for _, svc := range svcs {
463+
if svc.portName != portName {
464+
continue
465+
}
466+
if svc.proto != proto {
467+
continue
468+
}
469+
for _, t := range svc.target {
470+
srv = append(srv,
471+
&net.SRV{
472+
Target: t.name,
473+
Port: t.port,
474+
})
475+
476+
ip = append(ip, t.ip)
477+
}
478+
}
479+
if len(srv) > 0 {
480+
break
481+
}
482+
}
483+
return srv, ip, nil
484+
}
485+
428486
func (sb *sandbox) ResolveName(name string, ipType int) ([]net.IP, bool) {
429487
// Embedded server owns the docker network domain. Resolution should work
430488
// for both container_name and container_name.network_name

service.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ package libnetwork
22

33
import "net"
44

5+
// backing container or host's info
6+
type serviceTarget struct {
7+
name string
8+
ip net.IP
9+
port uint16
10+
}
11+
12+
type servicePorts struct {
13+
portName string
14+
proto string
15+
target []serviceTarget
16+
}
17+
518
type service struct {
619
name string
720
id string

0 commit comments

Comments
 (0)