blob: 57409e7e3508284a671ff3ec9f6c517561b6ccc1 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Matt Rosencrantz94502cf2015-03-18 09:43:44 -07005package rpc
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -08006
7import (
8 "fmt"
9 "net"
Asim Shankaraae31802015-01-22 11:59:42 -080010 "sort"
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080011
Jiri Simsa337af232015-02-27 14:36:46 -080012 "v.io/x/lib/vlog"
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080013
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -070014 "v.io/v23/naming"
15 "v.io/v23/verror"
16
Matt Rosencrantz9d3278a2015-03-11 14:58:34 -070017 "v.io/x/lib/netstate"
Matt Rosencrantzdbc1be22015-02-28 15:15:49 -080018 inaming "v.io/x/ref/profiles/internal/naming"
Matt Rosencrantz94502cf2015-03-18 09:43:44 -070019 "v.io/x/ref/profiles/internal/rpc/version"
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080020)
21
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -070022var (
23 // These errors are intended to be used as arguments to higher
24 // level errors and hence {1}{2} is omitted from their format
25 // strings to avoid repeating these n-times in the final error
26 // message visible to the user.
27 errMalformedEndpoint = reg(".errMalformedEndpoint", "malformed endpoint{:3}")
28 errUndesiredProtocol = reg(".errUndesiredProtocol", "undesired protocol{:3}")
29 errIncompatibleEndpointVersions = reg(".errIncompatibleEndpointVersions", "incompatible endpoint versions{:3}")
30 errNoCompatibleServers = reg(".errNoComaptibleServers", "failed to find any compatible servers{:3}")
31)
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080032
Asim Shankaraae31802015-01-22 11:59:42 -080033type serverLocality int
34
35const (
36 unknownNetwork serverLocality = iota
37 remoteNetwork
38 localNetwork
39)
40
41type sortableServer struct {
42 server naming.MountedServer
43 protocolRank int // larger values are preferred.
44 locality serverLocality // larger values are preferred.
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080045}
46
Asim Shankaraae31802015-01-22 11:59:42 -080047func (s *sortableServer) String() string {
48 return fmt.Sprintf("%v", s.server)
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080049}
50
Asim Shankaraae31802015-01-22 11:59:42 -080051type sortableServerList []sortableServer
52
53func (l sortableServerList) Len() int { return len(l) }
54func (l sortableServerList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
55func (l sortableServerList) Less(i, j int) bool {
56 if l[i].protocolRank == l[j].protocolRank {
57 return l[i].locality > l[j].locality
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080058 }
Asim Shankaraae31802015-01-22 11:59:42 -080059 return l[i].protocolRank > l[j].protocolRank
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080060}
61
Asim Shankaraae31802015-01-22 11:59:42 -080062func mkProtocolRankMap(list []string) map[string]int {
63 if len(list) == 0 {
64 return nil
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080065 }
Asim Shankaraae31802015-01-22 11:59:42 -080066 m := make(map[string]int)
67 for idx, protocol := range list {
68 m[protocol] = len(list) - idx
69 }
70 return m
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080071}
72
Asim Shankaraae31802015-01-22 11:59:42 -080073var defaultPreferredProtocolOrder = mkProtocolRankMap([]string{"unixfd", "wsh", "tcp4", "tcp", "*"})
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080074
Cosmos Nicolaouba6c68b2014-11-18 22:13:16 -080075// filterAndOrderServers returns a set of servers that are compatible with
76// the current client in order of 'preference' specified by the supplied
77// protocols and a notion of 'locality' according to the supplied protocol
78// list as follows:
79// - if the protocol parameter is non-empty, then only servers matching those
80// protocols are returned and the endpoints are ordered first by protocol
81// and then by locality within each protocol. If tcp4 and unixfd are requested
82// for example then only protocols that match tcp4 and unixfd will returned
83// with the tcp4 ones preceeding the unixfd ones.
84// - if the protocol parameter is empty, then a default protocol ordering
85// will be used, but unlike the previous case, any servers that don't support
86// these protocols will be returned also, but following the default
87// preferences.
Jungho Ahn25545d32015-01-26 15:14:14 -080088func filterAndOrderServers(servers []naming.MountedServer, protocols []string, ipnets []*net.IPNet) ([]naming.MountedServer, error) {
Asim Shankaraae31802015-01-22 11:59:42 -080089 vlog.VI(3).Infof("filterAndOrderServers%v: %v", protocols, servers)
90 var (
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -070091 errs = verror.SubErrs{}
Asim Shankaraae31802015-01-22 11:59:42 -080092 list = make(sortableServerList, 0, len(servers))
93 protoRanks = mkProtocolRankMap(protocols)
Asim Shankaraae31802015-01-22 11:59:42 -080094 )
95 if len(protoRanks) == 0 {
96 protoRanks = defaultPreferredProtocolOrder
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -080097 }
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -070098 adderr := func(name string, err error) {
99 errs = append(errs, verror.SubErr{Name: "server=" + name, Err: err, Options: verror.Print})
100 }
Asim Shankaraae31802015-01-22 11:59:42 -0800101 for _, server := range servers {
102 name := server.Server
103 ep, err := name2endpoint(name)
104 if err != nil {
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700105 adderr(name, verror.New(errMalformedEndpoint, nil, err))
Asim Shankaraae31802015-01-22 11:59:42 -0800106 continue
107 }
108 if err = version.CheckCompatibility(ep); err != nil {
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700109 // TODO(cnicolaou): convert rpc/version to verror.
110 adderr(name, verror.New(errIncompatibleEndpointVersions, nil, err))
Asim Shankaraae31802015-01-22 11:59:42 -0800111 continue
112 }
113 rank, err := protocol2rank(ep.Addr().Network(), protoRanks)
114 if err != nil {
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700115 adderr(name, err)
Asim Shankaraae31802015-01-22 11:59:42 -0800116 continue
117 }
118 list = append(list, sortableServer{
119 server: server,
120 protocolRank: rank,
121 locality: locality(ep, ipnets),
122 })
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -0800123 }
Asim Shankaraae31802015-01-22 11:59:42 -0800124 if len(list) == 0 {
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700125 return nil, verror.AddSubErrs(verror.New(errNoCompatibleServers, nil), nil, errs...)
Cosmos Nicolaou28f35c32014-12-01 20:36:27 -0800126 }
Asim Shankaraae31802015-01-22 11:59:42 -0800127 // TODO(ashankar): Don't have to use stable sorting, could
128 // just use sort.Sort. The only problem with that is the
129 // unittest.
130 sort.Stable(list)
131 // Convert to []naming.MountedServer
132 ret := make([]naming.MountedServer, len(list))
133 for idx, item := range list {
134 ret[idx] = item.server
135 }
136 return ret, nil
137}
Cosmos Nicolaou28f35c32014-12-01 20:36:27 -0800138
Asim Shankaraae31802015-01-22 11:59:42 -0800139// name2endpoint returns the naming.Endpoint encoded in a name.
140func name2endpoint(name string) (naming.Endpoint, error) {
141 addr := name
142 if naming.Rooted(name) {
143 addr, _ = naming.SplitAddressName(name)
144 }
145 return inaming.NewEndpoint(addr)
146}
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -0800147
Asim Shankaraae31802015-01-22 11:59:42 -0800148// protocol2rank returns the "rank" of a protocol (given a map of ranks).
149// The higher the rank, the more preferable the protocol.
150func protocol2rank(protocol string, ranks map[string]int) (int, error) {
151 if r, ok := ranks[protocol]; ok {
152 return r, nil
153 }
154 // Special case: if "wsh" has a rank but "wsh4"/"wsh6" don't,
155 // then they get the same rank as "wsh". Similar for "tcp" and "ws".
Jungho Ahn9a5b3072015-02-24 13:59:25 -0800156 //
157 // TODO(jhahn): We have similar protocol equivalency checks at a few places.
158 // Figure out a way for this mapping to be shared.
Asim Shankaraae31802015-01-22 11:59:42 -0800159 if p := protocol; p == "wsh4" || p == "wsh6" || p == "tcp4" || p == "tcp6" || p == "ws4" || p == "ws6" {
160 if r, ok := ranks[p[:len(p)-1]]; ok {
161 return r, nil
162 }
163 }
164 // "*" means that any protocol is acceptable.
165 if r, ok := ranks["*"]; ok {
166 return r, nil
167 }
168 // UnknownProtocol should be rare, it typically happens when
169 // the endpoint is described in <host>:<port> format instead of
170 // the full fidelity description (@<version>@<protocol>@...).
171 if protocol == naming.UnknownProtocol {
172 return -1, nil
173 }
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700174 return 0, verror.New(errUndesiredProtocol, nil, protocol)
Asim Shankaraae31802015-01-22 11:59:42 -0800175}
176
Jungho Ahn25545d32015-01-26 15:14:14 -0800177// locality returns the serverLocality to use given an endpoint and the
178// set of IP networks configured on this machine.
179func locality(ep naming.Endpoint, ipnets []*net.IPNet) serverLocality {
180 if len(ipnets) < 1 {
181 return unknownNetwork // 0 IP networks, locality doesn't matter.
182
183 }
184 host, _, err := net.SplitHostPort(ep.Addr().String())
185 if err != nil {
186 host = ep.Addr().String()
187 }
188 ip := net.ParseIP(host)
189 if ip == nil {
190 // Not an IP address (possibly not an IP network).
191 return unknownNetwork
192 }
193 for _, ipnet := range ipnets {
194 if ipnet.Contains(ip) {
195 return localNetwork
196 }
197 }
198 return remoteNetwork
199}
200
Asim Shankaraae31802015-01-22 11:59:42 -0800201// ipNetworks returns the IP networks on this machine.
202func ipNetworks() []*net.IPNet {
Cosmos Nicolaouaa87e292015-04-21 22:15:50 -0700203 ifcs, err := netstate.GetAllAddresses()
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -0800204 if err != nil {
Cosmos Nicolaouaa87e292015-04-21 22:15:50 -0700205 vlog.VI(5).Infof("netstate.GetAllAddresses failed: %v", err)
Asim Shankaraae31802015-01-22 11:59:42 -0800206 return nil
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -0800207 }
Asim Shankaraae31802015-01-22 11:59:42 -0800208 ret := make([]*net.IPNet, 0, len(ifcs))
209 for _, a := range ifcs {
Cosmos Nicolaouaa87e292015-04-21 22:15:50 -0700210 _, ipnet, err := net.ParseCIDR(a.String())
Asim Shankaraae31802015-01-22 11:59:42 -0800211 if err != nil {
Cosmos Nicolaouaa87e292015-04-21 22:15:50 -0700212 vlog.VI(5).Infof("net.ParseCIDR(%q) failed: %v", a, err)
Asim Shankaraae31802015-01-22 11:59:42 -0800213 continue
214 }
215 ret = append(ret, ipnet)
Cosmos Nicolaou4e8da642014-11-13 08:32:05 -0800216 }
Asim Shankaraae31802015-01-22 11:59:42 -0800217 return ret
218}