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
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
`Status: 200 OK`

View File

@ -438,19 +438,19 @@ void session_put_ref(MXS_SESSION* session);
*
* @param session Session to convert
* @param host Hostname of this server
*
* @param rdns Attempt reverse DNS on client ip address
* @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
*
* @param host Hostname of this server
*
* @param rdns Attempt reverse DNS on client ip addresses
* @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

View File

@ -409,6 +409,16 @@ module.exports = function() {
this.error = function(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) {
return yargs.epilog('List all client sessions.')
.usage('Usage: list sessions')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
}, function(argv) {
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'},
{'User': 'attributes.user'},
{'Host': 'attributes.remote'},

View File

@ -174,18 +174,30 @@ exports.builder = function(yargs) {
'the session is connected and the `Connection IDs` ' +
'field lists the IDs for those connections.')
.usage('Usage: show session <session>')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
}, function(argv) {
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) {
return yargs.epilog('Show detailed information about all sessions. ' +
'See `help show session` for more details.')
.usage('Usage: show sessions')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
}, function(argv) {
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) {

View File

@ -108,4 +108,14 @@ inline bool operator!=(const Host& l, const Host& 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 <vector>
#include <algorithm>
#include <arpa/inet.h>
#include <netdb.h>
namespace
{
@ -222,4 +224,52 @@ std::istream& operator>>(std::istream& is, Host& host)
host = Host(input);
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
{
bool option_rdns_is_on(const HttpRequest& request)
{
return request.get_option("rdns") == "true";
}
static bool drop_path_part(std::string& path)
{
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)
{
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)
@ -632,7 +639,8 @@ HttpResponse cb_get_session(const HttpRequest& request)
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);
return HttpResponse(MHD_HTTP_OK, json);
}

View File

@ -27,6 +27,7 @@
#include <sstream>
#include <maxbase/atomic.hh>
#include <maxbase/host.hh>
#include <maxscale/alloc.h>
#include <maxscale/clock.h>
#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);
}
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();
@ -761,7 +762,17 @@ json_t* session_json_data(const Session* session, const char* host)
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;
@ -798,18 +809,26 @@ json_t* session_json_data(const Session* session, const char* host)
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;
ss << MXS_JSON_API_SESSIONS << session->ses_id;
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
{
json_t* json;
const char* host;
SessionListData(const char* host, bool rdns)
: 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)
@ -818,15 +837,15 @@ bool seslist_cb(DCB* dcb, void* data)
{
SessionListData* d = (SessionListData*)data;
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;
}
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);
return mxs_json_resource(host, MXS_JSON_API_SESSIONS, data.json);
}