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:
@ -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`
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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'},
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user