1 #include <pichi/common/config.hpp>
2 // Include config.hpp first
3 #include <boost/asio/ip/network_v4.hpp>
4 #include <boost/asio/ip/network_v6.hpp>
5 #include <boost/asio/ip/tcp.hpp>
6 #include <cctype>
7 #include <iostream>
8 #include <maxminddb.h>
9 #include <numeric>
10 #include <pichi/api/router.hpp>
11 #include <pichi/common/asserts.hpp>
12 #include <pichi/common/endpoint.hpp>
13 #include <regex>
14 
15 using namespace std;
16 namespace ip = boost::asio::ip;
17 namespace sys = boost::system;
18 using ip::tcp;
19 
20 namespace pichi::api {
21 
22 namespace msg {
23 
24 static auto const DM_INVALID = "Invalid domain string"sv;
25 static auto const RG_INVALID = "Invalid IP range string"sv;
26 static auto const AT_INVALID = "Invalid adapter type string"sv;
27 
28 }  // namespace msg
29 
toLowerCase(string_view orig)30 static string toLowerCase(string_view orig)
31 {
32   auto lower = string(orig.size(), '\0');
33   transform(cbegin(orig), cend(orig), begin(lower),
34             [](auto c) { return static_cast<char>(tolower(c)); });
35   return lower;
36 }
37 
matchPattern(string_view remote,string_view pattern)38 bool matchPattern(string_view remote, string_view pattern)
39 {
40   return regex_search(cbegin(remote), cend(remote), regex{cbegin(pattern), cend(pattern)});
41 }
42 
matchDomain(string_view subdomain,string_view domain)43 bool matchDomain(string_view subdomain, string_view domain)
44 {
45   auto s = toLowerCase(subdomain);
46   auto d = toLowerCase(domain);
47   d.erase(0, d.find_first_not_of('.'));
48   assertFalse(d.empty(), PichiError::SEMANTIC_ERROR, msg::DM_INVALID);
49   assertFalse(s.empty(), msg::DM_INVALID);
50   assertFalse('.' == s.front(), msg::DM_INVALID);
51   return s == d ||                                    // same
52          (s.size() > d.size() &&                      // subdomain can not be shorter than domain
53           equal(crbegin(d), crend(d), crbegin(s)) &&  // subdomain ends up with domain
54           s[s.size() - d.size() - 1] == '.');
55 }
56 
Geo(char const * fn)57 Geo::Geo(char const* fn) : db_{make_unique<MMDB_s>()}
58 {
59   auto status = MMDB_open(fn, MMDB_MODE_MMAP, db_.get());
60   assertTrue(status == MMDB_SUCCESS, PichiError::MISC, MMDB_strerror(status));
61 }
62 
~Geo()63 Geo::~Geo() { MMDB_close(db_.get()); }
64 
match(tcp::endpoint const & endpoint,string_view country) const65 bool Geo::match(tcp::endpoint const& endpoint, string_view country) const
66 {
67   auto status = MMDB_SUCCESS;
68   auto result = MMDB_lookup_sockaddr(db_.get(), endpoint.data(), &status);
69 
70   // TODO log it
71   if (status != MMDB_SUCCESS || !result.found_entry) return false;
72 
73   auto entry = MMDB_entry_data_s{};
74   status = MMDB_get_value(&result.entry, &entry, "country", "iso_code", nullptr);
75 
76   // TODO log it
77   if (status != MMDB_SUCCESS || !entry.has_data) return false;
78   assertTrue(entry.type == MMDB_DATA_TYPE_UTF8_STRING, PichiError::MISC);
79 
80   return string_view{entry.utf8_string, entry.data_size} == country;
81 }
82 
generatePair(DelegateIterator it)83 Router::ValueType Router::generatePair(DelegateIterator it)
84 {
85   return make_pair(ref(it->first), ref(it->second.first));
86 }
87 
Router(char const * fn)88 Router::Router(char const* fn) : geo_{fn} {}
89 
route(Endpoint const & e,string_view ingress,AdapterType type,ResolvedResult const & r) const90 string_view Router::route(Endpoint const& e, string_view ingress, AdapterType type,
91                           ResolvedResult const& r) const
92 {
93   auto rule = "DEFAUTL rule"sv;
94   auto it = find_if(cbegin(route_.rules_), cend(route_.rules_), [&, this](auto&& rules) {
95     return any_of(cbegin(rules.first), cend(rules.first), [&, this](auto&& name) {
96       auto it = rules_.find(name);
97       assertFalse(it == cend(rules_));
98       auto& matchers = as_const(it->second.second);
99       auto b = any_of(cbegin(matchers), cend(matchers),
100                       [&](auto&& matcher) { return matcher(e, r, ingress, type); });
101       if (b) rule = name;
102       return b;
103     });
104   });
105   auto egress = string_view{it != cend(route_.rules_) ? it->second : *route_.default_};
106   cout << e.host_ << ":" << e.port_ << " -> " << egress << " (" << rule << ")" << endl;
107   return egress;
108 }
109 
update(string const & name,VO rvo)110 void Router::update(string const& name, VO rvo)
111 {
112   auto matchers = vector<Matcher>{};
113 
114   transform(cbegin(rvo.range_), cend(rvo.range_), back_inserter(matchers),
115             [](auto&& range) -> Matcher {
116               auto ec = sys::error_code{};
117               auto n4 = ip::make_network_v4(range, ec);
118               if (ec) {
119                 auto n6 = ip::make_network_v6(range, ec);
120                 assertFalse(static_cast<bool>(ec), PichiError::SEMANTIC_ERROR, msg::RG_INVALID);
121                 return [n6](auto&&, auto&& r, auto, auto) {
122                   return any_of(cbegin(r), cend(r), [n6](auto&& entry) {
123                     auto address = entry.endpoint().address();
124                     return address.is_v6() && n6.hosts().find(address.to_v6()) != cend(n6.hosts());
125                   });
126                 };
127               }
128               else
129                 return [n4](auto&&, auto&& r, auto, auto) {
130                   return any_of(cbegin(r), cend(r), [n4](auto&& entry) {
131                     auto address = entry.endpoint().address();
132                     return address.is_v4() && n4.hosts().find(address.to_v4()) != cend(n4.hosts());
133                   });
134                 };
135             });
136   transform(cbegin(rvo.ingress_), cend(rvo.ingress_), back_inserter(matchers), [](auto&& i) {
137     return [&i](auto&&, auto&&, auto ingress, auto) { return i == ingress; };
138   });
139   transform(cbegin(rvo.type_), cend(rvo.type_), back_inserter(matchers), [](auto t) {
140     // ingress type shouldn't be DIRECT or REJECT
141     assertFalse(t == AdapterType::DIRECT, PichiError::SEMANTIC_ERROR, msg::AT_INVALID);
142     assertFalse(t == AdapterType::REJECT, PichiError::SEMANTIC_ERROR, msg::AT_INVALID);
143     return [t](auto&&, auto&&, auto, auto type) { return t == type; };
144   });
145   transform(cbegin(rvo.pattern_), cend(rvo.pattern_), back_inserter(matchers), [](auto&& pattern) {
146     return [&pattern](auto&& e, auto&&, auto, auto) { return matchPattern(e.host_, pattern); };
147   });
148   transform(cbegin(rvo.domain_), cend(rvo.domain_), back_inserter(matchers), [](auto&& domain) {
149     return [&domain](auto&& e, auto&&, auto, auto) {
150       return e.type_ == EndpointType::DOMAIN_NAME && matchDomain(e.host_, domain);
151     };
152   });
153   transform(cbegin(rvo.country_), cend(rvo.country_), back_inserter(matchers),
154             [&geo = as_const(geo_)](auto&& country) {
155               return [&country, &geo](auto&&, auto&& r, auto, auto) {
156                 return any_of(cbegin(r), cend(r), [&geo, &country](auto&& entry) {
157                   return geo.match(entry.endpoint(), country);
158                 });
159               };
160             });
161 
162   rules_.insert_or_assign(name, pair(move(rvo), move(matchers)));
163 }
164 
erase(string_view name)165 void Router::erase(string_view name)
166 {
167   assertFalse(any_of(cbegin(route_.rules_), cend(route_.rules_),
168                      [name](auto&& items) {
169                        return any_of(cbegin(items.first), cend(items.first),
170                                      [name](auto&& rule) { return rule == name; });
171                      }),
172               PichiError::RES_IN_USE);
173   auto it = rules_.find(name);
174   if (it != std::end(rules_)) rules_.erase(it);
175 }
176 
begin() const177 Router::ConstIterator Router::begin() const noexcept
178 {
179   return {cbegin(rules_), cend(rules_), &Router::generatePair};
180 }
181 
end() const182 Router::ConstIterator Router::end() const noexcept
183 {
184   return {cend(rules_), cend(rules_), &Router::generatePair};
185 }
186 
isUsed(string_view egress) const187 bool Router::isUsed(string_view egress) const
188 {
189   return route_.default_ == egress || any_of(cbegin(route_.rules_), cend(route_.rules_),
190                                              [=](auto&& item) { return item.second == egress; });
191 }
192 
needResloving() const193 bool Router::needResloving() const { return needResolving_; }
194 
getRoute() const195 vo::Route Router::getRoute() const { return route_; }
196 
setRoute(vo::Route rvo)197 void Router::setRoute(vo::Route rvo)
198 {
199   needResolving_ = accumulate(
200       cbegin(rvo.rules_), cend(rvo.rules_), false, [this](auto needResolving, auto&& item) {
201         return accumulate(cbegin(item.first), cend(item.first), needResolving,
202                           [this](auto needResolving, auto&& name) {
203                             auto it = rules_.find(name);
204                             assertFalse(it == cend(rules_), PichiError::SEMANTIC_ERROR,
205                                         "Unknown rules"sv);
206                             auto& rule = it->second.first;
207                             return needResolving || !(rule.range_.empty() && rule.country_.empty());
208                           });
209       });
210   route_.rules_ = move(rvo.rules_);
211   if (rvo.default_.has_value()) route_.default_ = rvo.default_;
212 }
213 
214 }  // namespace pichi::api
215