Octoprint (#796)

* Octoprint: GUI for CA file, improvements

* Octoprint: Add GUI for Bonjour lookup, bugfixes

* Octoprint: Bonjour browser: Cleanup Perl interaction

* Octoprint: Bonjour: Perform several broadcast, UI fixes

* Octoprint: Add files to localization list

* Http: Disable CA File setting on SSL backends that don't support it
This commit is contained in:
Vojtech Kral 2018-03-15 18:06:26 +01:00 committed by bubnikv
parent 8d4b603572
commit c88d2780ce
21 changed files with 1587 additions and 1341 deletions

View file

@ -1,9 +1,7 @@
#include "Bonjour.hpp"
#include <iostream> // XXX
#include <cstdint>
#include <algorithm>
#include <unordered_map>
#include <array>
#include <vector>
#include <string>
@ -23,16 +21,18 @@ namespace asio = boost::asio;
using boost::asio::ip::udp;
// TODO: Fuzzing test (done without TXT)
// FIXME: check char retype to unsigned
namespace Slic3r {
// Minimal implementation of a MDNS/DNS-SD client
// This implementation is extremely simple, only the bits that are useful
// for very basic MDNS discovery are present.
// for basic MDNS discovery of OctoPi devices are present.
// However, the bits that are present are implemented with security in mind.
// Only fully correct DNS replies are allowed through.
// While decoding the decoder will bail the moment it encounters anything fishy.
// At least that's the idea. To help prove this is actually the case,
// the implementations has been tested with AFL.
struct DnsName: public std::string
{
@ -48,8 +48,7 @@ struct DnsName: public std::string
return boost::none;
}
// Check for recursion depth to prevent parsing names that are nested too deeply
// or end up cyclic:
// Check for recursion depth to prevent parsing names that are nested too deeply or end up cyclic:
if (depth >= MAX_RECURSION) {
return boost::none;
}
@ -443,6 +442,30 @@ private:
}
};
std::ostream& operator<<(std::ostream &os, const DnsMessage &msg)
{
os << "DnsMessage(ID: " << msg.header.id << ", "
<< "Q: " << (msg.question ? msg.question->name.c_str() : "none") << ", "
<< "A: " << (msg.rr_a ? msg.rr_a->ip.to_string() : "none") << ", "
<< "AAAA: " << (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none") << ", "
<< "services: [";
enum { SRV_PRINT_MAX = 3 };
unsigned i = 0;
for (const auto &sdpair : msg.sdmap) {
os << sdpair.first << ", ";
if (++i >= SRV_PRINT_MAX) {
os << "...";
break;
}
}
os << "])";
return os;
}
struct BonjourRequest
{
@ -515,6 +538,7 @@ struct Bonjour::priv
const std::string protocol;
const std::string service_dn;
unsigned timeout;
unsigned retries;
uint16_t rq_id;
std::vector<char> buffer;
@ -524,6 +548,7 @@ struct Bonjour::priv
priv(std::string service, std::string protocol);
std::string strip_service_dn(const std::string &service_name) const;
void udp_receive(udp::endpoint from, size_t bytes);
void lookup_perform();
};
@ -533,11 +558,26 @@ Bonjour::priv::priv(std::string service, std::string protocol) :
protocol(std::move(protocol)),
service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()),
timeout(10),
retries(1),
rq_id(0)
{
buffer.resize(DnsMessage::MAX_SIZE);
}
std::string Bonjour::priv::strip_service_dn(const std::string &service_name) const
{
if (service_name.size() <= service_dn.size()) {
return service_name;
}
auto needle = service_name.rfind(service_dn);
if (needle == service_name.size() - service_dn.size()) {
return service_name.substr(0, needle - 1);
} else {
return service_name;
}
}
void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
{
if (bytes == 0 || !replyfn) {
@ -557,7 +597,10 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
}
const auto &srv = *sdpair.second.srv;
BonjourReply reply(ip, sdpair.first, srv.hostname);
auto service_name = strip_service_dn(sdpair.first);
std::string path;
std::string version;
if (sdpair.second.txt) {
static const std::string tag_path = "path=";
@ -565,13 +608,14 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
for (const auto &value : sdpair.second.txt->values) {
if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) {
reply.path = value.substr(tag_path.size());
path = std::move(value.substr(tag_path.size()));
} else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
reply.version = value.substr(tag_version.size());
version = std::move(value.substr(tag_version.size()));
}
}
}
BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(path), std::move(version));
replyfn(std::move(reply));
}
}
@ -595,15 +639,26 @@ void Bonjour::priv::lookup_perform()
udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
socket.send_to(asio::buffer(brq->data), mcast);
bool timeout = false;
bool expired = false;
bool retry = false;
asio::deadline_timer timer(io_service);
timer.expires_from_now(boost::posix_time::seconds(10));
timer.async_wait([=, &timeout](const error_code &error) {
timeout = true;
if (self->completefn) {
self->completefn();
retries--;
std::function<void(const error_code &)> timer_handler = [&](const error_code &error) {
if (retries == 0 || error) {
expired = true;
if (self->completefn) {
self->completefn();
}
} else {
retry = true;
retries--;
timer.expires_from_now(boost::posix_time::seconds(timeout));
timer.async_wait(timer_handler);
}
});
};
timer.expires_from_now(boost::posix_time::seconds(timeout));
timer.async_wait(timer_handler);
udp::endpoint recv_from;
const auto recv_handler = [&](const error_code &error, size_t bytes) {
@ -612,8 +667,11 @@ void Bonjour::priv::lookup_perform()
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
while (io_service.run_one()) {
if (timeout) {
if (expired) {
socket.cancel();
} else if (retry) {
retry = false;
socket.send_to(asio::buffer(brq->data), mcast);
} else {
buffer.resize(DnsMessage::MAX_SIZE);
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
@ -626,13 +684,39 @@ void Bonjour::priv::lookup_perform()
// API - public part
BonjourReply::BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname) :
BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version) :
ip(std::move(ip)),
port(port),
service_name(std::move(service_name)),
hostname(std::move(hostname)),
path("/"),
version("Unknown")
{}
path(path.empty() ? std::move(std::string("/")) : std::move(path)),
version(version.empty() ? std::move(std::string("Unknown")) : std::move(version))
{
std::string proto;
std::string port_suffix;
if (port == 443) { proto = "https://"; }
if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); }
if (this->path[0] != '/') { this->path.insert(0, 1, '/'); }
full_address = proto + ip.to_string() + port_suffix;
if (this->path != "/") { full_address += path; }
}
bool BonjourReply::operator==(const BonjourReply &other) const
{
return this->full_address == other.full_address
&& this->service_name == other.service_name;
}
bool BonjourReply::operator<(const BonjourReply &other) const
{
if (this->ip != other.ip) {
// So that the common case doesn't involve string comparison
return this->ip < other.ip;
} else {
auto cmp = this->full_address.compare(other.full_address);
return cmp != 0 ? cmp < 0 : this->service_name < other.service_name;
}
}
std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
{
@ -641,6 +725,7 @@ std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
return os;
}
Bonjour::Bonjour(std::string service, std::string protocol) :
p(new priv(std::move(service), std::move(protocol)))
{}
@ -660,6 +745,12 @@ Bonjour& Bonjour::set_timeout(unsigned timeout)
return *this;
}
Bonjour& Bonjour::set_retries(unsigned retries)
{
if (p && retries > 0) { p->retries = retries; }
return *this;
}
Bonjour& Bonjour::on_reply(ReplyFn fn)
{
if (p) { p->replyfn = std::move(fn); }
@ -677,7 +768,7 @@ Bonjour::Ptr Bonjour::lookup()
auto self = std::make_shared<Bonjour>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self](){
auto io_thread = std::thread([self]() {
self->p->lookup_perform();
});
self->p->io_thread = std::move(io_thread);
@ -687,18 +778,4 @@ Bonjour::Ptr Bonjour::lookup()
}
void Bonjour::pokus() // XXX
{
auto bonjour = Bonjour("octoprint")
.set_timeout(15)
.on_reply([](BonjourReply &&reply) {
std::cerr << "BonjourReply: " << reply << std::endl;
})
.on_complete([](){
std::cerr << "MDNS lookup complete" << std::endl;
})
.lookup();
}
}