Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // 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 Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 5 | package rpc |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 6 | |
| 7 | import ( |
| 8 | "fmt" |
| 9 | "net" |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 10 | "sort" |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 11 | |
Jiri Simsa | 337af23 | 2015-02-27 14:36:46 -0800 | [diff] [blame] | 12 | "v.io/x/lib/vlog" |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 13 | |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 14 | "v.io/v23/naming" |
| 15 | "v.io/v23/verror" |
| 16 | |
Matt Rosencrantz | 9d3278a | 2015-03-11 14:58:34 -0700 | [diff] [blame] | 17 | "v.io/x/lib/netstate" |
Matt Rosencrantz | dbc1be2 | 2015-02-28 15:15:49 -0800 | [diff] [blame] | 18 | inaming "v.io/x/ref/profiles/internal/naming" |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 19 | "v.io/x/ref/profiles/internal/rpc/version" |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 20 | ) |
| 21 | |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 22 | var ( |
| 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 Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 32 | |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 33 | type serverLocality int |
| 34 | |
| 35 | const ( |
| 36 | unknownNetwork serverLocality = iota |
| 37 | remoteNetwork |
| 38 | localNetwork |
| 39 | ) |
| 40 | |
| 41 | type sortableServer struct { |
| 42 | server naming.MountedServer |
| 43 | protocolRank int // larger values are preferred. |
| 44 | locality serverLocality // larger values are preferred. |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 45 | } |
| 46 | |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 47 | func (s *sortableServer) String() string { |
| 48 | return fmt.Sprintf("%v", s.server) |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 49 | } |
| 50 | |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 51 | type sortableServerList []sortableServer |
| 52 | |
| 53 | func (l sortableServerList) Len() int { return len(l) } |
| 54 | func (l sortableServerList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } |
| 55 | func (l sortableServerList) Less(i, j int) bool { |
| 56 | if l[i].protocolRank == l[j].protocolRank { |
| 57 | return l[i].locality > l[j].locality |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 58 | } |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 59 | return l[i].protocolRank > l[j].protocolRank |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 60 | } |
| 61 | |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 62 | func mkProtocolRankMap(list []string) map[string]int { |
| 63 | if len(list) == 0 { |
| 64 | return nil |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 65 | } |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 66 | m := make(map[string]int) |
| 67 | for idx, protocol := range list { |
| 68 | m[protocol] = len(list) - idx |
| 69 | } |
| 70 | return m |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 71 | } |
| 72 | |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 73 | var defaultPreferredProtocolOrder = mkProtocolRankMap([]string{"unixfd", "wsh", "tcp4", "tcp", "*"}) |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 74 | |
Cosmos Nicolaou | ba6c68b | 2014-11-18 22:13:16 -0800 | [diff] [blame] | 75 | // 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 Ahn | 25545d3 | 2015-01-26 15:14:14 -0800 | [diff] [blame] | 88 | func filterAndOrderServers(servers []naming.MountedServer, protocols []string, ipnets []*net.IPNet) ([]naming.MountedServer, error) { |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 89 | vlog.VI(3).Infof("filterAndOrderServers%v: %v", protocols, servers) |
| 90 | var ( |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 91 | errs = verror.SubErrs{} |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 92 | list = make(sortableServerList, 0, len(servers)) |
| 93 | protoRanks = mkProtocolRankMap(protocols) |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 94 | ) |
| 95 | if len(protoRanks) == 0 { |
| 96 | protoRanks = defaultPreferredProtocolOrder |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 97 | } |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 98 | adderr := func(name string, err error) { |
| 99 | errs = append(errs, verror.SubErr{Name: "server=" + name, Err: err, Options: verror.Print}) |
| 100 | } |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 101 | for _, server := range servers { |
| 102 | name := server.Server |
| 103 | ep, err := name2endpoint(name) |
| 104 | if err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 105 | adderr(name, verror.New(errMalformedEndpoint, nil, err)) |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 106 | continue |
| 107 | } |
| 108 | if err = version.CheckCompatibility(ep); err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 109 | // TODO(cnicolaou): convert rpc/version to verror. |
| 110 | adderr(name, verror.New(errIncompatibleEndpointVersions, nil, err)) |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 111 | continue |
| 112 | } |
| 113 | rank, err := protocol2rank(ep.Addr().Network(), protoRanks) |
| 114 | if err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 115 | adderr(name, err) |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 116 | continue |
| 117 | } |
| 118 | list = append(list, sortableServer{ |
| 119 | server: server, |
| 120 | protocolRank: rank, |
| 121 | locality: locality(ep, ipnets), |
| 122 | }) |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 123 | } |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 124 | if len(list) == 0 { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 125 | return nil, verror.AddSubErrs(verror.New(errNoCompatibleServers, nil), nil, errs...) |
Cosmos Nicolaou | 28f35c3 | 2014-12-01 20:36:27 -0800 | [diff] [blame] | 126 | } |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 127 | // 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 Nicolaou | 28f35c3 | 2014-12-01 20:36:27 -0800 | [diff] [blame] | 138 | |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 139 | // name2endpoint returns the naming.Endpoint encoded in a name. |
| 140 | func 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 Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 147 | |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 148 | // protocol2rank returns the "rank" of a protocol (given a map of ranks). |
| 149 | // The higher the rank, the more preferable the protocol. |
| 150 | func 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 Ahn | 9a5b307 | 2015-02-24 13:59:25 -0800 | [diff] [blame] | 156 | // |
| 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 Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 159 | 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 Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 174 | return 0, verror.New(errUndesiredProtocol, nil, protocol) |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 175 | } |
| 176 | |
Jungho Ahn | 25545d3 | 2015-01-26 15:14:14 -0800 | [diff] [blame] | 177 | // locality returns the serverLocality to use given an endpoint and the |
| 178 | // set of IP networks configured on this machine. |
| 179 | func 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 Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 201 | // ipNetworks returns the IP networks on this machine. |
| 202 | func ipNetworks() []*net.IPNet { |
Cosmos Nicolaou | aa87e29 | 2015-04-21 22:15:50 -0700 | [diff] [blame^] | 203 | ifcs, err := netstate.GetAllAddresses() |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 204 | if err != nil { |
Cosmos Nicolaou | aa87e29 | 2015-04-21 22:15:50 -0700 | [diff] [blame^] | 205 | vlog.VI(5).Infof("netstate.GetAllAddresses failed: %v", err) |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 206 | return nil |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 207 | } |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 208 | ret := make([]*net.IPNet, 0, len(ifcs)) |
| 209 | for _, a := range ifcs { |
Cosmos Nicolaou | aa87e29 | 2015-04-21 22:15:50 -0700 | [diff] [blame^] | 210 | _, ipnet, err := net.ParseCIDR(a.String()) |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 211 | if err != nil { |
Cosmos Nicolaou | aa87e29 | 2015-04-21 22:15:50 -0700 | [diff] [blame^] | 212 | vlog.VI(5).Infof("net.ParseCIDR(%q) failed: %v", a, err) |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 213 | continue |
| 214 | } |
| 215 | ret = append(ret, ipnet) |
Cosmos Nicolaou | 4e8da64 | 2014-11-13 08:32:05 -0800 | [diff] [blame] | 216 | } |
Asim Shankar | aae3180 | 2015-01-22 11:59:42 -0800 | [diff] [blame] | 217 | return ret |
| 218 | } |