Skip to content

Commit 77c6ddd

Browse files
committed
pan: Add some documentation for host resolution and context to tests
cleanup host resolution cache result from parsing hosts file for SCION addresses until file is modified
1 parent cec92ce commit 77c6ddd

File tree

6 files changed

+83
-45
lines changed

6 files changed

+83
-45
lines changed

pkg/pan/dns_txt.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@ import (
2222
)
2323

2424
type dnsResolver struct{}
25+
2526
var _ resolver = &dnsResolver{}
2627

28+
// Resolve the name via DNS to return one scionAddr or an error.
2729
func (d *dnsResolver) Resolve(ctx context.Context, name string) (saddr scionAddr, err error) {
2830
addresses, err := queryTXTRecord(ctx, name)
2931
if err != nil {
3032
return scionAddr{}, err
3133
}
32-
if len(addresses) == 0 {
33-
return scionAddr{}, HostNotFoundError{Host: name}
34-
}
3534
var perr error
3635
for _, addr := range addresses {
3736
saddr, perr = parseSCIONAddr(addr)
@@ -42,27 +41,29 @@ func (d *dnsResolver) Resolve(ctx context.Context, name string) (saddr scionAddr
4241
return scionAddr{}, fmt.Errorf("error parsing TXT SCION address records: %w", perr)
4342
}
4443

45-
func queryTXTRecord(ctx context.Context, query string) (address []string, err error) {
46-
if !strings.HasSuffix(query,".") {
47-
query += "."
44+
// queryTXTRecord queries the DNS for DNS TXT record(s) specifying the SCION address(es) for host.
45+
// Returns either at least one address, or else an error, of type HostNotFoundError if no matching record was found.
46+
func queryTXTRecord(ctx context.Context, host string) (addresses []string, err error) {
47+
if !strings.HasSuffix(host, ".") {
48+
host += "."
4849
}
4950
resolver := net.Resolver{}
50-
txtRecords, err := resolver.LookupHost(ctx, query)
51+
txtRecords, err := resolver.LookupHost(ctx, host)
5152
if dnsError, ok := err.(*net.DNSError); ok {
5253
if dnsError.IsNotFound {
53-
return address, HostNotFoundError{query}
54+
return addresses, HostNotFoundError{host}
5455
}
5556
}
5657
if err != nil {
57-
return address, err
58+
return addresses, err
5859
}
5960
for _, txt := range txtRecords {
6061
if strings.HasPrefix(txt, "scion=") {
61-
address = append(address, strings.TrimPrefix(txt, "scion="))
62+
addresses = append(addresses, strings.TrimPrefix(txt, "scion="))
6263
}
6364
}
64-
if len(address) == 0 {
65-
return address, HostNotFoundError{query}
65+
if len(addresses) == 0 {
66+
return addresses, HostNotFoundError{host}
6667
}
67-
return address, nil
68+
return addresses, nil
6869
}

pkg/pan/hosts.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,22 @@ func (resolvers resolverList) Resolve(ctx context.Context, name string) (scionAd
8888
var errHostNotFound HostNotFoundError
8989
var rerr error
9090
for _, resolver := range resolvers {
91-
if resolver != nil {
92-
addr, err := resolver.Resolve(ctx, name)
93-
if err == nil {
94-
return addr, nil
95-
} else if !errors.As(err, &errHostNotFound) {
96-
// do not directly fail on first resolver error
97-
rerr = err
98-
}
91+
if resolver == nil {
92+
// skip RAINS resolver when disabled
93+
continue
94+
}
95+
// check ctx to avoid unnecessary calls with already expired context
96+
select {
97+
case <-ctx.Done():
98+
break
99+
default:
100+
}
101+
addr, err := resolver.Resolve(ctx, name)
102+
if err == nil {
103+
return addr, nil
104+
} else if !errors.As(err, &errHostNotFound) {
105+
// do not directly fail on first resolver error
106+
rerr = err
99107
}
100108
}
101109
if rerr != nil {

pkg/pan/hosts_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package pan
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"testing"
2021

@@ -53,7 +54,7 @@ func TestHostsfileResolver(t *testing.T) {
5354
{"foobar", assertErrHostNotFound, scionAddr{}},
5455
}
5556
for _, c := range cases {
56-
actual, err := resolver.Resolve(c.name)
57+
actual, err := resolver.Resolve(context.TODO(), c.name)
5758
if !c.assertErr(t, err) {
5859
continue
5960
}
@@ -63,7 +64,7 @@ func TestHostsfileResolver(t *testing.T) {
6364

6465
func TestHostsfileResolverNonexisting(t *testing.T) {
6566
resolver := &hostsfileResolver{"non_existing_hosts_file"}
66-
_, err := resolver.Resolve("something")
67+
_, err := resolver.Resolve(context.TODO(), "something")
6768
assert.Error(t, err)
6869
}
6970

@@ -92,7 +93,7 @@ func TestResolverList(t *testing.T) {
9293
{"boo", assertErrHostNotFound, scionAddr{}},
9394
}
9495
for _, c := range cases {
95-
actual, err := resolver.Resolve(c.name)
96+
actual, err := resolver.Resolve(context.TODO(), c.name)
9697
c.assertErr(t, err)
9798
assert.Equal(t, c.expected, actual)
9899
}
@@ -107,7 +108,9 @@ type dummyResolver struct {
107108
hosts map[string]scionAddr
108109
}
109110

110-
func (r dummyResolver) Resolve(name string) (scionAddr, error) {
111+
var _ resolver = &dummyResolver{}
112+
113+
func (r dummyResolver) Resolve(ctx context.Context, name string) (scionAddr, error) {
111114
if h, ok := r.hosts[name]; ok {
112115
return h, nil
113116
} else {

pkg/pan/hostsfile.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,29 @@ import (
2020
"fmt"
2121
"os"
2222
"strings"
23+
"sync"
24+
"time"
2325
)
2426

2527
type hostsTable map[string]scionAddr
2628

29+
var cachedHostsTable struct {
30+
mapping hostsTable
31+
updated time.Time
32+
sync.Mutex
33+
}
34+
2735
// hostsfileResolver is an implementation of the resolver interface, backed
2836
// by an /etc/hosts-like file.
2937
type hostsfileResolver struct {
3038
path string
3139
}
40+
3241
var _ resolver = &hostsfileResolver{}
3342

3443
func (r *hostsfileResolver) Resolve(ctx context.Context, name string) (scionAddr, error) {
35-
// Note: obviously not perfectly elegant to parse the entire file for
36-
// every query. However, properly caching this and still always provide
37-
// fresh results after changes to the hosts file seems like a bigger task and
38-
// for now that would be overkill.
44+
cachedHostsTable.Lock()
45+
defer cachedHostsTable.Unlock()
3946
table, err := loadHostsFile(r.path)
4047
if err != nil {
4148
return scionAddr{}, fmt.Errorf("error loading %s: %w", r.path, err)
@@ -47,17 +54,34 @@ func (r *hostsfileResolver) Resolve(ctx context.Context, name string) (scionAddr
4754
return addr, nil
4855
}
4956

57+
// loadHostsFile provides a cached copy of the hostsTable or loads and parses the host file at path if it was modified
58+
// since the last update.
5059
func loadHostsFile(path string) (hostsTable, error) {
51-
file, err := os.Open(path)
60+
stat, err := os.Stat(path)
5261
if os.IsNotExist(err) {
5362
// not existing file treated like an empty file,
5463
// just return an empty table
5564
return hostsTable(nil), nil
56-
} else if err != nil {
57-
return nil, err
65+
}
66+
if err != nil {
67+
return hostsTable(nil), err
68+
}
69+
if stat.ModTime().Before(cachedHostsTable.updated) {
70+
return cachedHostsTable.mapping, nil
71+
}
72+
73+
file, err := os.Open(path)
74+
if err != nil {
75+
return hostsTable(nil), err
5876
}
5977
defer file.Close()
60-
return parseHostsFile(file)
78+
mapping, err := parseHostsFile(file)
79+
if err != nil {
80+
return hostsTable(nil), err
81+
}
82+
cachedHostsTable.mapping = mapping
83+
cachedHostsTable.updated = time.Now()
84+
return cachedHostsTable.mapping, nil
6185
}
6286

6387
func parseHostsFile(file *os.File) (hostsTable, error) {

pkg/pan/pan.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ import (
108108
"time"
109109
)
110110

111+
var defaultResolveTimeout = time.Second
112+
111113
// ResolveUDPAddr parses the address and resolves the hostname.
112114
// The address can be of the form of a SCION address (i.e. of the form "ISD-AS,[IP]:port")
113115
// or in the form of "hostname:port".
@@ -122,13 +124,13 @@ import (
122124
//
123125
// Returns HostNotFoundError if none of the sources did resolve the hostname.
124126
func ResolveUDPAddr(address string) (addr UDPAddr, err error) {
125-
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
126-
return ResolveUDPAddrContext(ctx, cancel, address)
127+
ctx, cancel := context.WithTimeout(context.Background(), defaultResolveTimeout)
128+
defer cancel()
129+
return ResolveUDPAddrContext(ctx, address)
127130
}
128131

129132
// ResolveUDPAddrContext resolves address, taking a context and a cancel function, see ResolveUDPAddr.
130-
func ResolveUDPAddrContext(ctx context.Context, cancel context.CancelFunc, address string) (addr UDPAddr, err error) {
131-
defer cancel()
133+
func ResolveUDPAddrContext(ctx context.Context, address string) (addr UDPAddr, err error) {
132134
done := make(chan struct{})
133135
go func() {
134136
defer close(done)
@@ -142,7 +144,6 @@ func ResolveUDPAddrContext(ctx context.Context, cancel context.CancelFunc, addre
142144
return
143145
}
144146

145-
146147
// HostNotFoundError is returned by ResolveUDPAddr when the name was not found, but
147148
// otherwise no error occurred.
148149
type HostNotFoundError struct {

pkg/pan/rains.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func init() {
3535
}
3636

3737
type rainsResolver struct{}
38+
3839
var _ resolver = &rainsResolver{}
3940

4041
func (r *rainsResolver) Resolve(ctx context.Context, name string) (scionAddr, error) {
@@ -46,7 +47,7 @@ func (r *rainsResolver) Resolve(ctx context.Context, name string) (scionAddr, er
4647
// nobody to ask, so we won't get a reply
4748
return scionAddr{}, HostNotFoundError{name}
4849
}
49-
return rainsQuery(server, name)
50+
return rainsQuery(ctx, server, name)
5051
}
5152

5253
func readRainsConfig() (UDPAddr, error) {
@@ -63,12 +64,12 @@ func readRainsConfig() (UDPAddr, error) {
6364
return address, nil
6465
}
6566

66-
func rainsQuery(server UDPAddr, hostname string) (scionAddr, error) {
67+
func rainsQuery(ctx context.Context, server UDPAddr, hostname string) (scionAddr, error) {
6768
const (
68-
ctx = "." // use global context
69-
qType = rains.OTScionAddr // request SCION addresses
70-
expire = 5 * time.Minute // sensible expiry date?
71-
timeout = 500 * time.Millisecond // timeout for query
69+
rainsCtx = "." // use global context
70+
qType = rains.OTScionAddr // request SCION addresses
71+
expire = 5 * time.Minute // sensible expiry date?
72+
timeout = 500 * time.Millisecond // timeout for query
7273
)
7374
qOpts := []rains.Option{} // no options
7475

@@ -78,7 +79,7 @@ func rainsQuery(server UDPAddr, hostname string) (scionAddr, error) {
7879
// TODO(chaehni): This call can sometimes cause a timeout even though the server is reachable (see issue #221)
7980
// The timeout value has been decreased to counter this behavior until the problem is resolved.
8081
srv := server.snetUDPAddr()
81-
reply, err := rains.Query(hostname, ctx, []rains.Type{qType}, qOpts, expire, timeout, srv)
82+
reply, err := rains.Query(hostname, rainsCtx, []rains.Type{qType}, qOpts, expire, timeout, srv)
8283
if err != nil {
8384
return scionAddr{}, fmt.Errorf("address for host %q not found: %w", hostname, err)
8485
}

0 commit comments

Comments
 (0)