1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #include "mongo/platform/basic.h"
32 
33 #include "mongo/util/net/hostandport.h"
34 
35 #include <boost/functional/hash.hpp>
36 
37 #include "mongo/base/status.h"
38 #include "mongo/base/status_with.h"
39 #include "mongo/base/string_data.h"
40 #include "mongo/bson/util/builder.h"
41 #include "mongo/db/server_options.h"
42 #include "mongo/util/assert_util.h"
43 #include "mongo/util/mongoutils/str.h"
44 #include "mongo/util/stringutils.h"
45 
46 namespace mongo {
47 
parse(StringData text)48 StatusWith<HostAndPort> HostAndPort::parse(StringData text) {
49     HostAndPort result;
50     Status status = result.initialize(text);
51     if (!status.isOK()) {
52         return StatusWith<HostAndPort>(status);
53     }
54     return StatusWith<HostAndPort>(result);
55 }
56 
HostAndPort()57 HostAndPort::HostAndPort() : _port(-1) {}
58 
HostAndPort(StringData text)59 HostAndPort::HostAndPort(StringData text) {
60     uassertStatusOK(initialize(text));
61 }
62 
HostAndPort(const std::string & h,int p)63 HostAndPort::HostAndPort(const std::string& h, int p) : _host(h), _port(p) {}
64 
HostAndPort(SockAddr addr)65 HostAndPort::HostAndPort(SockAddr addr) : _addr(std::move(addr)) {
66     uassertStatusOK(initialize(_addr->toString(true)));
67 }
68 
operator <(const HostAndPort & r) const69 bool HostAndPort::operator<(const HostAndPort& r) const {
70     const int cmp = host().compare(r.host());
71     if (cmp)
72         return cmp < 0;
73     return port() < r.port();
74 }
75 
operator ==(const HostAndPort & r) const76 bool HostAndPort::operator==(const HostAndPort& r) const {
77     return host() == r.host() && port() == r.port();
78 }
79 
port() const80 int HostAndPort::port() const {
81     if (hasPort())
82         return _port;
83     return ServerGlobalParams::DefaultDBPort;
84 }
85 
isLocalHost() const86 bool HostAndPort::isLocalHost() const {
87     return (_host == "localhost" || str::startsWith(_host.c_str(), "127.") || _host == "::1" ||
88             _host == "anonymous unix socket" || _host.c_str()[0] == '/'  // unix socket
89             );
90 }
91 
isDefaultRoute() const92 bool HostAndPort::isDefaultRoute() const {
93     if (_host == "0.0.0.0") {
94         return true;
95     }
96 
97     // There are multiple ways to write IPv6 addresses.
98     // We're looking for any representation of the address "0:0:0:0:0:0:0:0".
99     // A single sequence of "0" bytes in an IPv6 address may be represented as "::",
100     // so we must also match addresses like "::" or "0::0:0".
101     // Return false if a character other than ':' or '0' is contained in the address.
102     auto firstNonDefaultIPv6Char =
103         std::find_if(std::begin(_host), std::end(_host), [](const char& c) {
104             return c != ':' && c != '0' && c != '[' && c != ']';
105         });
106     return firstNonDefaultIPv6Char == std::end(_host);
107 }
108 
toString() const109 std::string HostAndPort::toString() const {
110     StringBuilder ss;
111     append(ss);
112     return ss.str();
113 }
114 
append(StringBuilder & ss) const115 void HostAndPort::append(StringBuilder& ss) const {
116     // wrap ipv6 addresses in []s for roundtrip-ability
117     if (host().find(':') != std::string::npos) {
118         ss << '[' << host() << ']';
119     } else {
120         ss << host();
121     }
122     if (host().find('/') == std::string::npos) {
123         ss << ':' << port();
124     }
125 }
126 
empty() const127 bool HostAndPort::empty() const {
128     return _host.empty() && _port < 0;
129 }
130 
initialize(StringData s)131 Status HostAndPort::initialize(StringData s) {
132     size_t colonPos = s.rfind(':');
133     StringData hostPart = s.substr(0, colonPos);
134 
135     // handle ipv6 hostPart (which we require to be wrapped in []s)
136     const size_t openBracketPos = s.find('[');
137     const size_t closeBracketPos = s.find(']');
138     if (openBracketPos != std::string::npos) {
139         if (openBracketPos != 0) {
140             return Status(ErrorCodes::FailedToParse,
141                           str::stream() << "'[' present, but not first character in "
142                                         << s.toString());
143         }
144         if (closeBracketPos == std::string::npos) {
145             return Status(ErrorCodes::FailedToParse,
146                           str::stream() << "ipv6 address is missing closing ']' in hostname in "
147                                         << s.toString());
148         }
149 
150         hostPart = s.substr(openBracketPos + 1, closeBracketPos - openBracketPos - 1);
151         // prevent accidental assignment of port to the value of the final portion of hostPart
152         if (colonPos < closeBracketPos) {
153             // If the last colon is inside the brackets, then there must not be a port.
154             if (s.size() != closeBracketPos + 1) {
155                 return Status(ErrorCodes::FailedToParse,
156                               str::stream() << "missing colon after ']' before the port in "
157                                             << s.toString());
158             }
159             colonPos = std::string::npos;
160         } else if (colonPos != closeBracketPos + 1) {
161             return Status(ErrorCodes::FailedToParse,
162                           str::stream() << "Extraneous characters between ']' and pre-port ':'"
163                                         << " in "
164                                         << s.toString());
165         }
166     } else if (closeBracketPos != std::string::npos) {
167         return Status(ErrorCodes::FailedToParse,
168                       str::stream() << "']' present without '[' in " << s.toString());
169     } else if (s.find(':') != colonPos) {
170         return Status(ErrorCodes::FailedToParse,
171                       str::stream() << "More than one ':' detected. If this is an ipv6 address,"
172                                     << " it needs to be surrounded by '[' and ']'; "
173                                     << s.toString());
174     }
175 
176     if (hostPart.empty()) {
177         return Status(ErrorCodes::FailedToParse,
178                       str::stream() << "Empty host component parsing HostAndPort from \""
179                                     << escape(s.toString())
180                                     << "\"");
181     }
182 
183     int port;
184     if (colonPos != std::string::npos) {
185         const StringData portPart = s.substr(colonPos + 1);
186         Status status = parseNumberFromStringWithBase(portPart, 10, &port);
187         if (!status.isOK()) {
188             return status;
189         }
190         if (port <= 0 || port > 65535) {
191             return Status(ErrorCodes::FailedToParse,
192                           str::stream() << "Port number " << port
193                                         << " out of range parsing HostAndPort from \""
194                                         << escape(s.toString())
195                                         << "\"");
196         }
197     } else {
198         port = -1;
199     }
200     _host = hostPart.toString();
201     _port = port;
202     return Status::OK();
203 }
204 
operator <<(std::ostream & os,const HostAndPort & hp)205 std::ostream& operator<<(std::ostream& os, const HostAndPort& hp) {
206     return os << hp.toString();
207 }
208 
209 template <typename Allocator>
operator <<(StringBuilderImpl<Allocator> & os,const HostAndPort & hp)210 StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, const HostAndPort& hp) {
211     return os << hp.toString();
212 }
213 
214 template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&,
215                                                        const HostAndPort&);
216 template StringBuilderImpl<SharedBufferAllocator>& operator<<(
217     StringBuilderImpl<SharedBufferAllocator>&, const HostAndPort&);
218 
219 }  // namespace mongo
220 
221 MONGO_HASH_NAMESPACE_START
operator ()(const mongo::HostAndPort & host) const222 size_t hash<mongo::HostAndPort>::operator()(const mongo::HostAndPort& host) const {
223     hash<int> intHasher;
224     size_t hash = intHasher(host.port());
225     boost::hash_combine(hash, host.host());
226     return hash;
227 }
228 MONGO_HASH_NAMESPACE_END
229