MXS-1973 Support reverse DNS for client hostnames in MaxCtrl

May slow maxscale down when used. Only supported for "list sessions",
"show sessions" and "show session <id>".
This commit is contained in:
Esa Korhonen
2019-05-06 18:44:23 +03:00
parent 0e0342e657
commit e3b5ba9620
9 changed files with 137 additions and 18 deletions

View File

@ -15,6 +15,10 @@ GET /v1/sessions/:id
Get a single session. _:id_ must be a valid session ID. The session ID is the Get a single session. _:id_ must be a valid session ID. The session ID is the
same that is exposed to the client as the connection ID. same that is exposed to the client as the connection ID.
This endpoint also supports the `rdns=true` parameter, which instructs MaxScale to
perform reverse DNS on the client IP address. As this requires communicating with
an external server, the operation may be expensive.
#### Response #### Response
`Status: 200 OK` `Status: 200 OK`

View File

@ -438,19 +438,19 @@ void session_put_ref(MXS_SESSION* session);
* *
* @param session Session to convert * @param session Session to convert
* @param host Hostname of this server * @param host Hostname of this server
* * @param rdns Attempt reverse DNS on client ip address
* @return New JSON object or NULL on error * @return New JSON object or NULL on error
*/ */
json_t* session_to_json(const MXS_SESSION* session, const char* host); json_t* session_to_json(const MXS_SESSION* session, const char* host, bool rdns);
/** /**
* @brief Convert all sessions to JSON * @brief Convert all sessions to JSON
* *
* @param host Hostname of this server * @param host Hostname of this server
* * @param rdns Attempt reverse DNS on client ip addresses
* @return A JSON array with all sessions * @return A JSON array with all sessions
*/ */
json_t* session_list_to_json(const char* host); json_t* session_list_to_json(const char* host, bool rdns);
/** /**
* Qualify the session for connection pooling * Qualify the session for connection pooling

View File

@ -409,6 +409,16 @@ module.exports = function() {
this.error = function(err) { this.error = function(err) {
return Promise.reject(colors.red('Error: ') + err) return Promise.reject(colors.red('Error: ') + err)
} }
this.rDnsOption = {
shortname: 'rdns',
optionOn: 'rdns=true',
definition : {
describe: 'Reverse DNS on client IP. May slow MaxScale down.',
type: 'bool',
default: false
}
}
} }

View File

@ -115,9 +115,15 @@ exports.builder = function(yargs) {
.command('sessions', 'List sessions', function(yargs) { .command('sessions', 'List sessions', function(yargs) {
return yargs.epilog('List all client sessions.') return yargs.epilog('List all client sessions.')
.usage('Usage: list sessions') .usage('Usage: list sessions')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
}, function(argv) { }, function(argv) {
maxctrl(argv, function(host) { maxctrl(argv, function(host) {
return getCollection(host, 'sessions',[ var resource = 'sessions'
if (argv[this.rDnsOption.shortname]) {
resource += '?' + this.rDnsOption.optionOn
}
return getCollection(host, resource,[
{'Id': 'id'}, {'Id': 'id'},
{'User': 'attributes.user'}, {'User': 'attributes.user'},
{'Host': 'attributes.remote'}, {'Host': 'attributes.remote'},

View File

@ -174,18 +174,30 @@ exports.builder = function(yargs) {
'the session is connected and the `Connection IDs` ' + 'the session is connected and the `Connection IDs` ' +
'field lists the IDs for those connections.') 'field lists the IDs for those connections.')
.usage('Usage: show session <session>') .usage('Usage: show session <session>')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
}, function(argv) { }, function(argv) {
maxctrl(argv, function(host) { maxctrl(argv, function(host) {
return getResource(host, 'sessions/' + argv.session, session_fields) var resource = 'sessions/' + argv.session
if (argv[this.rDnsOption.shortname]) {
resource += '?' + this.rDnsOption.optionOn
}
return getResource(host, resource, session_fields)
}) })
}) })
.command('sessions', 'Show all sessions', function(yargs) { .command('sessions', 'Show all sessions', function(yargs) {
return yargs.epilog('Show detailed information about all sessions. ' + return yargs.epilog('Show detailed information about all sessions. ' +
'See `help show session` for more details.') 'See `help show session` for more details.')
.usage('Usage: show sessions') .usage('Usage: show sessions')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
}, function(argv) { }, function(argv) {
maxctrl(argv, function(host) { maxctrl(argv, function(host) {
return getCollectionAsResource(host, 'sessions/', session_fields) var resource = 'sessions/'
if (argv[this.rDnsOption.shortname]) {
resource += '?' + this.rDnsOption.optionOn
}
return getCollectionAsResource(host, resource, session_fields)
}) })
}) })
.command('filter <filter>', 'Show filter', function(yargs) { .command('filter <filter>', 'Show filter', function(yargs) {

View File

@ -108,4 +108,14 @@ inline bool operator!=(const Host& l, const Host& r)
{ {
return !(l == r); return !(l == r);
} }
/**
* Perform reverse DNS on an IP address. This may involve network communication so can be slow.
*
* @param ip IP to convert to hostname
* @param output Where to write the output. If operation fails, original IP is written.
* @return True on success
*/
bool reverse_dns(const std::string& ip, std::string* output);
} }

View File

@ -16,6 +16,8 @@
#include <ostream> #include <ostream>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <arpa/inet.h>
#include <netdb.h>
namespace namespace
{ {
@ -222,4 +224,52 @@ std::istream& operator>>(std::istream& is, Host& host)
host = Host(input); host = Host(input);
return is; return is;
} }
bool reverse_dns(const std::string& ip, std::string* output)
{
sockaddr_storage socket_address;
memset(&socket_address, 0, sizeof(socket_address));
socklen_t slen = 0;
if (is_valid_ipv4(ip))
{
// Casts between the different sockaddr-types should work.
int family = AF_INET;
auto sa_in = reinterpret_cast<sockaddr_in*>(&socket_address);
if (inet_pton(family, ip.c_str(), &sa_in->sin_addr) == 1)
{
sa_in->sin_family = family;
slen = sizeof(sockaddr_in);
}
}
else if (is_valid_ipv6(ip))
{
int family = AF_INET6;
auto sa_in6 = reinterpret_cast<sockaddr_in6*>(&socket_address);
if (inet_pton(family, ip.c_str(), &sa_in6->sin6_addr) == 1)
{
sa_in6->sin6_family = family;
slen = sizeof(sockaddr_in6);
}
}
bool success = false;
if (slen > 0)
{
char host[NI_MAXHOST];
auto sa = reinterpret_cast<sockaddr*>(&socket_address);
if (getnameinfo(sa, slen, host, sizeof(host), nullptr, 0, NI_NAMEREQD) == 0)
{
*output = host;
success = true;
}
}
if (!success)
{
*output = ip;
}
return success;
}
} }

View File

@ -178,6 +178,12 @@ bool Resource::requires_body() const
namespace namespace
{ {
bool option_rdns_is_on(const HttpRequest& request)
{
return request.get_option("rdns") == "true";
}
static bool drop_path_part(std::string& path) static bool drop_path_part(std::string& path)
{ {
size_t pos = path.find_last_of('/'); size_t pos = path.find_last_of('/');
@ -622,7 +628,8 @@ HttpResponse cb_get_monitor(const HttpRequest& request)
HttpResponse cb_all_sessions(const HttpRequest& request) HttpResponse cb_all_sessions(const HttpRequest& request)
{ {
return HttpResponse(MHD_HTTP_OK, session_list_to_json(request.host())); bool rdns = option_rdns_is_on(request);
return HttpResponse(MHD_HTTP_OK, session_list_to_json(request.host(), rdns));
} }
HttpResponse cb_get_session(const HttpRequest& request) HttpResponse cb_get_session(const HttpRequest& request)
@ -632,7 +639,8 @@ HttpResponse cb_get_session(const HttpRequest& request)
if (session) if (session)
{ {
json_t* json = session_to_json(session, request.host()); bool rdns = option_rdns_is_on(request);
json_t* json = session_to_json(session, request.host(), rdns);
session_put_ref(session); session_put_ref(session);
return HttpResponse(MHD_HTTP_OK, json); return HttpResponse(MHD_HTTP_OK, json);
} }

View File

@ -27,6 +27,7 @@
#include <sstream> #include <sstream>
#include <maxbase/atomic.hh> #include <maxbase/atomic.hh>
#include <maxbase/host.hh>
#include <maxscale/alloc.h> #include <maxscale/alloc.h>
#include <maxscale/clock.h> #include <maxscale/clock.h>
#include <maxscale/dcb.hh> #include <maxscale/dcb.hh>
@ -714,7 +715,7 @@ uint64_t session_get_next_id()
return mxb::atomic::add(&this_unit.next_session_id, 1, mxb::atomic::RELAXED); return mxb::atomic::add(&this_unit.next_session_id, 1, mxb::atomic::RELAXED);
} }
json_t* session_json_data(const Session* session, const char* host) json_t* session_json_data(const Session* session, const char* host, bool rdns)
{ {
json_t* data = json_object(); json_t* data = json_object();
@ -761,7 +762,17 @@ json_t* session_json_data(const Session* session, const char* host)
if (session->client_dcb->remote) if (session->client_dcb->remote)
{ {
json_object_set_new(attr, "remote", json_string(session->client_dcb->remote)); string result_address;
auto remote = session->client_dcb->remote;
if (rdns)
{
maxbase::reverse_dns(remote, &result_address);
}
else
{
result_address = remote;
}
json_object_set_new(attr, "remote", json_string(result_address.c_str()));
} }
struct tm result; struct tm result;
@ -798,18 +809,26 @@ json_t* session_json_data(const Session* session, const char* host)
return data; return data;
} }
json_t* session_to_json(const MXS_SESSION* session, const char* host) json_t* session_to_json(const MXS_SESSION* session, const char* host, bool rdns)
{ {
stringstream ss; stringstream ss;
ss << MXS_JSON_API_SESSIONS << session->ses_id; ss << MXS_JSON_API_SESSIONS << session->ses_id;
const Session* s = static_cast<const Session*>(session); const Session* s = static_cast<const Session*>(session);
return mxs_json_resource(host, ss.str().c_str(), session_json_data(s, host)); return mxs_json_resource(host, ss.str().c_str(), session_json_data(s, host, rdns));
} }
struct SessionListData struct SessionListData
{ {
json_t* json; SessionListData(const char* host, bool rdns)
const char* host; : json(json_array())
, host(host)
, rdns(rdns)
{
}
json_t* json {nullptr};
const char* host {nullptr};
bool rdns {false};
}; };
bool seslist_cb(DCB* dcb, void* data) bool seslist_cb(DCB* dcb, void* data)
@ -818,15 +837,15 @@ bool seslist_cb(DCB* dcb, void* data)
{ {
SessionListData* d = (SessionListData*)data; SessionListData* d = (SessionListData*)data;
Session* session = static_cast<Session*>(dcb->session); Session* session = static_cast<Session*>(dcb->session);
json_array_append_new(d->json, session_json_data(session, d->host)); json_array_append_new(d->json, session_json_data(session, d->host, d->rdns));
} }
return true; return true;
} }
json_t* session_list_to_json(const char* host) json_t* session_list_to_json(const char* host, bool rdns)
{ {
SessionListData data = {json_array(), host}; SessionListData data(host, rdns);
dcb_foreach(seslist_cb, &data); dcb_foreach(seslist_cb, &data);
return mxs_json_resource(host, MXS_JSON_API_SESSIONS, data.json); return mxs_json_resource(host, MXS_JSON_API_SESSIONS, data.json);
} }