Bonjour - resolve hostname to IP using mdns.

Octoprint - on Windows use Bonjour resolving to get IP address before the http connection. Only use Windows address resolving if Bonjour couldn't resolve.
This commit is contained in:
David Kocik 2022-04-20 13:50:43 +02:00
parent fbdf777898
commit d2c6f597d2
4 changed files with 577 additions and 18 deletions

View File

@ -13,6 +13,8 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp> #include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <boost/format.hpp> #include <boost/format.hpp>
#include <boost/thread.hpp>
#include <boost/enable_shared_from_this.hpp>
using boost::optional; using boost::optional;
using boost::system::error_code; using boost::system::error_code;
@ -238,6 +240,7 @@ struct DnsRR_A
enum { TAG = 0x1 }; enum { TAG = 0x1 };
asio::ip::address_v4 ip; asio::ip::address_v4 ip;
std::string name;
static void decode(optional<DnsRR_A> &result, const DnsResource &rr) static void decode(optional<DnsRR_A> &result, const DnsResource &rr)
{ {
@ -255,6 +258,7 @@ struct DnsRR_AAAA
enum { TAG = 0x1c }; enum { TAG = 0x1c };
asio::ip::address_v6 ip; asio::ip::address_v6 ip;
std::string name;
static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr) static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr)
{ {
@ -438,8 +442,14 @@ private:
void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset, const Bonjour::TxtKeys &txt_keys) void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset, const Bonjour::TxtKeys &txt_keys)
{ {
switch (rr.type) { switch (rr.type) {
case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break; case DnsRR_A::TAG:
case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break; DnsRR_A::decode(this->rr_a, rr);
this->rr_a->name = rr.name;
break;
case DnsRR_AAAA::TAG:
DnsRR_AAAA::decode(this->rr_aaaa, rr);
this->rr_aaaa->name = rr.name;
break;
case DnsRR_SRV::TAG: { case DnsRR_SRV::TAG: {
auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset); auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset);
if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); } if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); }
@ -484,20 +494,23 @@ std::ostream& operator<<(std::ostream &os, const DnsMessage &msg)
struct BonjourRequest struct BonjourRequest
{ {
static const asio::ip::address_v4 MCAST_IP4; static const asio::ip::address_v4 MCAST_IP4;
static const asio::ip::address_v6 MCAST_IP6;
static const uint16_t MCAST_PORT; static const uint16_t MCAST_PORT;
std::vector<char> data; std::vector<char> data;
static optional<BonjourRequest> make(const std::string &service, const std::string &protocol); static optional<BonjourRequest> make_PTR(const std::string &service, const std::string &protocol);
static optional<BonjourRequest> make_A(const std::string& hostname);
static optional<BonjourRequest> make_AAAA(const std::string& hostname);
private: private:
BonjourRequest(std::vector<char> &&data) : data(std::move(data)) {} BonjourRequest(std::vector<char> &&data) : data(std::move(data)) {}
}; };
const asio::ip::address_v4 BonjourRequest::MCAST_IP4{ 0xe00000fb }; const asio::ip::address_v4 BonjourRequest::MCAST_IP4{ 0xe00000fb };
const asio::ip::address_v6 BonjourRequest::MCAST_IP6 = asio::ip::make_address_v6("ff02::fb");
const uint16_t BonjourRequest::MCAST_PORT = 5353; const uint16_t BonjourRequest::MCAST_PORT = 5353;
optional<BonjourRequest> BonjourRequest::make(const std::string &service, const std::string &protocol) optional<BonjourRequest> BonjourRequest::make_PTR(const std::string &service, const std::string &protocol)
{ {
if (service.size() > 15 || protocol.size() > 15) { if (service.size() > 15 || protocol.size() > 15) {
return boost::none; return boost::none;
@ -535,6 +548,75 @@ optional<BonjourRequest> BonjourRequest::make(const std::string &service, const
return BonjourRequest(std::move(data)); return BonjourRequest(std::move(data));
} }
optional<BonjourRequest> BonjourRequest::make_A(const std::string& hostname)
{
// todo: why is this and what is real max
if (hostname.size() > 30) {
return boost::none;
}
std::vector<char> data;
data.reserve(hostname.size() + 18);
// Add metadata
static const unsigned char rq_meta[] = {
0x00, 0x00, // Query ID (zero for mDNS)
0x00, 0x00, // Flags
0x00, 0x01, // One query
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zero Answer, Authority, and Additional RRs
};
std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
// Add hostname without .local
data.push_back(hostname.size());
data.insert(data.end(), hostname.begin(), hostname.end());
// Add the rest of A record
static const unsigned char ptr_tail[] = {
0x05, // length of "local"
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00,// "local" string and terminator
0x00, 0x01, // Type A
0x00, 0xff, // Class - 01 is internet 0xff is any
};
std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
return BonjourRequest(std::move(data));
}
optional<BonjourRequest> BonjourRequest::make_AAAA(const std::string& hostname)
{
// todo: why is this and what is real max
if (hostname.size() > 30) {
return boost::none;
}
std::vector<char> data;
data.reserve(hostname.size() + 18);
// Add metadata
static const unsigned char rq_meta[] = {
0x00, 0x00, // Query ID (zero for mDNS)
0x00, 0x00, // Flags
0x00, 0x01, // One query
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zero Answer, Authority, and Additional RRs
};
std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
// Add hostname without .local
data.push_back(hostname.size());
data.insert(data.end(), hostname.begin(), hostname.end());
// Add the rest of A record
static const unsigned char ptr_tail[] = {
0x05, // length of "local"
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, // "local" string and terminator
0x00, 0x1c, // Type AAAA
0x00, 0xff, // Class - 01 is internet 0xff is any
};
std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
return BonjourRequest(std::move(data));
}
// API - private part // API - private part
@ -546,17 +628,22 @@ struct Bonjour::priv
TxtKeys txt_keys; TxtKeys txt_keys;
unsigned timeout; unsigned timeout;
unsigned retries; unsigned retries;
std::string hostname;
std::vector<BonjourReply> replies;
std::vector<char> buffer; std::vector<char> buffer;
std::thread io_thread; std::thread io_thread;
Bonjour::ReplyFn replyfn; Bonjour::ReplyFn replyfn;
Bonjour::CompleteFn completefn; Bonjour::CompleteFn completefn;
Bonjour::ResolveFn resolvefn;
priv(std::string &&service); priv(std::string &&service);
std::string strip_service_dn(const std::string &service_name) const; std::string strip_service_dn(const std::string &service_name) const;
void udp_receive(udp::endpoint from, size_t bytes); void udp_receive_lookup(udp::endpoint from, size_t bytes);
void lookup_perform(); void lookup_perform();
void resolve_perform();
}; };
Bonjour::priv::priv(std::string &&service) Bonjour::priv::priv(std::string &&service)
@ -582,7 +669,7 @@ std::string Bonjour::priv::strip_service_dn(const std::string &service_name) con
} }
} }
void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes) void Bonjour::priv::udp_receive_lookup(udp::endpoint from, size_t bytes)
{ {
if (bytes == 0 || !replyfn) { if (bytes == 0 || !replyfn) {
return; return;
@ -621,7 +708,7 @@ void Bonjour::priv::lookup_perform()
{ {
service_dn = (boost::format("_%1%._%2%.local") % service % protocol).str(); service_dn = (boost::format("_%1%._%2%.local") % service % protocol).str();
const auto brq = BonjourRequest::make(service, protocol); const auto brq = BonjourRequest::make_PTR(service, protocol);
if (!brq) { if (!brq) {
return; return;
} }
@ -659,7 +746,7 @@ void Bonjour::priv::lookup_perform()
udp::endpoint recv_from; udp::endpoint recv_from;
const auto recv_handler = [&](const error_code &error, size_t bytes) { const auto recv_handler = [&](const error_code &error, size_t bytes) {
if (!error) { self->udp_receive(recv_from, bytes); } if (!error) { self->udp_receive_lookup(recv_from, bytes); }
}; };
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler); socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
@ -678,6 +765,282 @@ void Bonjour::priv::lookup_perform()
} }
} }
class ResolveSocket;
// One session for socket::async_receive_from().
// Keeps buffer of data so it is not overwritten when another async_receive_from starts.
struct UdpSession : boost::enable_shared_from_this<UdpSession>{
UdpSession(ResolveSocket* sckt, Bonjour::ReplyFn rfn) : socket(sckt), replyfn(rfn)
{
buffer.resize(DnsMessage::MAX_SIZE);
}
void handle_receive(const error_code& error, size_t bytes);
udp::endpoint remote_endpoint;
std::vector<char> buffer;
ResolveSocket* socket;
Bonjour::ReplyFn replyfn;
};
// Udp socket for Bonjour::resolve_perform().
// Starts receiving answers after first send() call until io_service is stopped.
class ResolveSocket
{
private:
typedef boost::shared_ptr<UdpSession> SharedSession;
Bonjour::ReplyFn replyfn; // this doesnt call same fn as replyfn of Bonjour class
asio::ip::address multicast_address;
udp::socket socket;
udp::endpoint mcast_endpoint;
boost::shared_ptr< boost::asio::io_service > io_service;
std::string hostname;
boost::optional<BonjourRequest> request_A;
boost::optional<BonjourRequest> request_AAAA;
asio::ip::multicast::outbound_interface outbound_interface;
public:
ResolveSocket(const std::string& hostname, Bonjour::ReplyFn replyfn, const asio::ip::address& multicast_address, const asio::ip::address& interface_address, boost::shared_ptr< boost::asio::io_service > io_service);
void send();
void async_receive();
void cancel() { socket.cancel(); }
std::string get_hostname() { return hostname; }
private:
void receive_handler(SharedSession session, const error_code& error, size_t bytes);
};
ResolveSocket::ResolveSocket(const std::string& hostname, Bonjour::ReplyFn replyfn, const asio::ip::address& multicast_address, const asio::ip::address& interface_address, boost::shared_ptr< boost::asio::io_service > io_service)
: hostname(hostname)
, replyfn(replyfn)
, multicast_address(multicast_address)
, socket(*io_service)
, io_service(io_service)
{
assert(!hostname.empty() && replyfn);
try {
// open socket
boost::asio::ip::udp::endpoint listen_endpoint(multicast_address.is_v4() ? udp::v4() : udp::v6(), BonjourRequest::MCAST_PORT);
socket.open(listen_endpoint.protocol());
// set socket to listen
socket.set_option(udp::socket::reuse_address(true));
socket.bind(listen_endpoint);
if (interface_address.is_v4()) {
// listen for multicast on given interface
socket.set_option(boost::asio::ip::multicast::join_group(multicast_address.to_v4(), interface_address.to_v4()));
// send to interface
socket.set_option(asio::ip::multicast::outbound_interface(interface_address.to_v4()));
} else {
// listen for multicast on given interface
socket.set_option(boost::asio::ip::multicast::join_group(multicast_address.to_v6(), interface_address.to_v6().scope_id()));
// send to interface
socket.set_option(asio::ip::multicast::outbound_interface(interface_address.to_v6().scope_id()));
}
mcast_endpoint = udp::endpoint(multicast_address, BonjourRequest::MCAST_PORT);
BOOST_LOG_TRIVIAL(info) << "Socket created. Multicast: " << multicast_address << ". Interface: " << interface_address;
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
}
}
void ResolveSocket::send()
{
try {
if (!request_A) {
// BonjourRequest::make_A / AAAA is now implemented to add .local correctly after the hostname.
// If that is unsufficient, we need to change make_A / AAAA and pass full hostname.
std::string trimmed_hostname = hostname;
if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos)
trimmed_hostname = trimmed_hostname.substr(0, dot_pos);
request_A = BonjourRequest::make_A(trimmed_hostname);
}
if (!request_AAAA) {
std::string trimmed_hostname = hostname;
if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos)
trimmed_hostname = trimmed_hostname.substr(0, dot_pos);
request_AAAA = BonjourRequest::make_AAAA(trimmed_hostname);
}
// multicast both queries
socket.send_to(asio::buffer(request_A->data), mcast_endpoint);
socket.send_to(asio::buffer(request_AAAA->data), mcast_endpoint);
// Should we care if this is called while already receiving? (async_receive call from receive_handler)
async_receive();
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
}
}
void ResolveSocket::async_receive()
{
try {
// our session to hold the buffer + endpoint
auto session = boost::make_shared<UdpSession>(this, replyfn);
socket.async_receive_from(asio::buffer(session->buffer, session->buffer.size())
, session->remote_endpoint
, boost::bind(&ResolveSocket::receive_handler, this, session, asio::placeholders::error, asio::placeholders::bytes_transferred));
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
}
}
void ResolveSocket::receive_handler(SharedSession session, const error_code& error, size_t bytes)
{
// let io_service to handle the datagram on session
io_service->post(bind(&UdpSession::handle_receive, session, error, bytes));
// immediately accept new datagrams
async_receive();
}
void UdpSession::handle_receive(const error_code& error, size_t bytes)
{
if (error) {
// todo: what level? do we even log? There might be callbacks when timer runs out
BOOST_LOG_TRIVIAL(info) << error.message();
return;
}
if (bytes == 0 || !replyfn) {
// todo: log something?
return;
}
buffer.resize(bytes);
#if 0
// this is log of buffer, be careful with logging here - called from async_receive
std::string str;
char const hex_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
for (size_t i = 0; i < buffer.size(); i++) {
const char ch = buffer[i];
str += hex_chars[(ch & 0xF0) >> 4];
str += hex_chars[(ch & 0x0F) >> 0];
}
BOOST_LOG_TRIVIAL(debug) << remote_endpoint.address()<< " " << str;
#endif
// decode buffer, txt keys are not needed for A / AAAA answer
auto dns_msg = DnsMessage::decode(buffer, Bonjour::TxtKeys());
if (dns_msg) {
asio::ip::address ip;
std::string answer_name;
if (dns_msg->rr_a) {
ip = dns_msg->rr_a->ip;
answer_name = dns_msg->rr_a->name;
}
else if (dns_msg->rr_aaaa) {
ip = dns_msg->rr_aaaa->ip;
answer_name = dns_msg->rr_aaaa->name;
}
else
return; // not matching query type with answer type
if (!answer_name.empty()) {
// transform both strings to lower. Should we really do it?
std::string name_tolower = answer_name;
std::transform(name_tolower.begin(), name_tolower.end(), name_tolower.begin(),
[](unsigned char c) { return std::tolower(c); });
std::string hostname_tolower = socket->get_hostname();
std::transform(hostname_tolower.begin(), hostname_tolower.end(), hostname_tolower.begin(),
[](unsigned char c) { return std::tolower(c); });
if (name_tolower == hostname_tolower) {
BonjourReply reply(ip, 0, std::string(), answer_name, BonjourReply::TxtData());
replyfn(std::move(reply));
}
}
}
}
void Bonjour::priv::resolve_perform()
{
// reply callback is shared to every UDPSession which is called asyn
boost::mutex replies_guard;
std::vector<BonjourReply> replies;
// examples would probably store [self] to the lambda (and the timer one), is it ok not to do it? (Should be c++03)
const auto reply_callback = [&rpls = replies, &guard = replies_guard](BonjourReply&& reply)
{
guard.lock();
if (std::find(rpls.begin(), rpls.end(), reply) == rpls.end())
rpls.push_back(reply);
guard.unlock();
};
boost::shared_ptr< boost::asio::io_service > io_service(
new boost::asio::io_service
);
std::vector<ResolveSocket*> sockets;
// resolve intefaces - from PR#6646
std::vector<boost::asio::ip::address> interfaces;
asio::ip::udp::resolver resolver(*io_service);
// ipv4 interfaces
auto results = resolver.resolve(udp::v4(), asio::ip::host_name(), "");
for (auto const& r : results) {
auto const addr = r.endpoint().address();
if (addr.is_loopback()) continue;
interfaces.emplace_back(addr);
}
// create ipv4 socket for each interface
// each will send to querry to for both ipv4 and ipv6
for (const auto intrfc : interfaces)
sockets.emplace_back(new ResolveSocket(hostname, reply_callback, BonjourRequest::MCAST_IP4, intrfc, io_service));
// ipv6 interfaces
interfaces.clear();
results = resolver.resolve(udp::v6(), asio::ip::host_name(), "");
for (auto const& r : results) {
auto const addr = r.endpoint().address();
if (addr.is_loopback()) continue;
interfaces.emplace_back(addr);
}
// create ipv6 socket for each interface
// each will send to querry to for both ipv4 and ipv6
for (const auto intrfc : interfaces)
sockets.emplace_back(new ResolveSocket(hostname, reply_callback, BonjourRequest::MCAST_IP6, intrfc, io_service));
try {
// send first queries
for each (auto * socket in sockets)
socket->send();
// timer settings
asio::deadline_timer timer(*io_service);
retries--;
std::function<void(const error_code&)> timer_handler = [&](const error_code& error) {
replies_guard.lock();
int replies_count = replies.size();
replies_guard.unlock();
// end
if (retries == 0 || error || replies_count > 0) {
// is this correct ending?
io_service->stop();
replies_guard.lock();
if (replies_count > 0 && resolvefn) {
resolvefn(replies);
}
replies_guard.unlock();
// restart timer
} else {
retries--;
timer.expires_from_now(boost::posix_time::seconds(timeout));
timer.async_wait(timer_handler);
// trigger another round of queries
for each (auto * socket in sockets)
socket->send();
}
};
// start timer
timer.expires_from_now(boost::posix_time::seconds(timeout));
timer.async_wait(timer_handler);
// start io_service, it will run until it has something to do - so in this case until stop is called in timer
io_service->run();
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
}
}
// API - public part // API - public part
@ -770,6 +1133,12 @@ Bonjour& Bonjour::set_timeout(unsigned timeout)
return *this; return *this;
} }
Bonjour& Bonjour::set_hostname(const std::string& hostname)
{
if (p) { p->hostname = hostname; }
return *this;
}
Bonjour& Bonjour::set_retries(unsigned retries) Bonjour& Bonjour::set_retries(unsigned retries)
{ {
if (p && retries > 0) { p->retries = retries; } if (p && retries > 0) { p->retries = retries; }
@ -788,6 +1157,12 @@ Bonjour& Bonjour::on_complete(CompleteFn fn)
return *this; return *this;
} }
Bonjour& Bonjour::on_resolve(ResolveFn fn)
{
if (p) { p->resolvefn = std::move(fn); }
return *this;
}
Bonjour::Ptr Bonjour::lookup() Bonjour::Ptr Bonjour::lookup()
{ {
auto self = std::make_shared<Bonjour>(std::move(*this)); auto self = std::make_shared<Bonjour>(std::move(*this));
@ -803,4 +1178,26 @@ Bonjour::Ptr Bonjour::lookup()
} }
Bonjour::Ptr Bonjour::resolve()
{
auto self = std::make_shared<Bonjour>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self]() {
self->p->resolve_perform();
});
self->p->io_thread = std::move(io_thread);
} }
return self;
}
void Bonjour::resolve_sync()
{
if (p)
p->resolve_perform();
}
}

View File

@ -49,6 +49,7 @@ public:
typedef std::shared_ptr<Bonjour> Ptr; typedef std::shared_ptr<Bonjour> Ptr;
typedef std::function<void(BonjourReply &&)> ReplyFn; typedef std::function<void(BonjourReply &&)> ReplyFn;
typedef std::function<void()> CompleteFn; typedef std::function<void()> CompleteFn;
typedef std::function<void(const std::vector<BonjourReply>&)> ResolveFn;
typedef std::set<std::string> TxtKeys; typedef std::set<std::string> TxtKeys;
Bonjour(std::string service); Bonjour(std::string service);
@ -66,10 +67,21 @@ public:
// Timeout is per one retry, ie. total time spent listening = retries * timeout. // Timeout is per one retry, ie. total time spent listening = retries * timeout.
// If retries > 1, then care needs to be taken as more than one reply from the same service may be received. // If retries > 1, then care needs to be taken as more than one reply from the same service may be received.
// sets hostname queried by resolve()
Bonjour& set_hostname(const std::string& hostname);
Bonjour& on_reply(ReplyFn fn); Bonjour& on_reply(ReplyFn fn);
Bonjour& on_complete(CompleteFn fn); Bonjour& on_complete(CompleteFn fn);
Bonjour& on_resolve(ResolveFn fn);
// lookup all devices by given TxtKeys
// each correct reply is passed back in ReplyFn, finishes with CompleteFn
Ptr lookup(); Ptr lookup();
// performs resolving of hostname into vector of ip adresses passed back by ResolveFn
// needs set_hostname and on_resolve to be called before.
Ptr resolve();
// resolve on the current thread
void resolve_sync();
private: private:
std::unique_ptr<priv> p; std::unique_ptr<priv> p;
}; };

View File

@ -8,6 +8,7 @@
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/asio.hpp>
#include <curl/curl.h> #include <curl/curl.h>
@ -18,7 +19,7 @@
#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI.hpp"
#include "Http.hpp" #include "Http.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
#include "Bonjour.hpp"
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
@ -103,11 +104,60 @@ OctoPrint::OctoPrint(DynamicPrintConfig *config) :
const char* OctoPrint::get_name() const { return "OctoPrint"; } const char* OctoPrint::get_name() const { return "OctoPrint"; }
bool OctoPrint::test_with_resolved_ip(wxString &msg) const
{
// Since the request is performed synchronously here,
// it is ok to refer to `msg` from within the closure
const char* name = get_name();
bool res = true;
// Msg contains ip string.
auto url = substitute_host(make_url("api/version"), GUI::into_u8(msg));
msg.Clear();
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
auto http = Http::get(std::move(url));
set_auth(http);
http
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
res = false;
msg = format_error(body, error, status);
})
.on_complete([&, this](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
try {
std::stringstream ss(body);
pt::ptree ptree;
pt::read_json(ss, ptree);
if (!ptree.get_optional<std::string>("api")) {
res = false;
return;
}
const auto text = ptree.get_optional<std::string>("text");
res = validate_version_text(text);
if (!res) {
msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
}
}
catch (const std::exception&) {
res = false;
msg = "Could not parse server response.";
}
})
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
.perform_sync();
return res;
}
bool OctoPrint::test(wxString& msg) const bool OctoPrint::test(wxString& msg) const
{ {
// Since the request is performed synchronously here, // Since the request is performed synchronously here,
// it is ok to refer to `msg` from within the closure // it is ok to refer to `msg` from within the closure
const char *name = get_name(); const char *name = get_name();
bool res = true; bool res = true;
@ -159,6 +209,7 @@ bool OctoPrint::test(wxString &msg) const
return res; return res;
} }
wxString OctoPrint::get_test_ok_msg () const wxString OctoPrint::get_test_ok_msg () const
{ {
return _(L("Connection to OctoPrint works correctly.")); return _(L("Connection to OctoPrint works correctly."));
@ -173,6 +224,100 @@ wxString OctoPrint::get_test_failed_msg (wxString &msg) const
} }
bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
{
#ifndef WIN32
return upload_inner_with_host(upload_data, prorgess_fn, error_fn);
#endif // !WIN32
// decide what to do based on m_host - resolve hostname or upload to ip
std::vector<boost::asio::ip::address> resolved_addr;
boost::system::error_code ec;
boost::asio::ip::address host_ip = boost::asio::ip::make_address(m_host, ec);
if (!ec) {
resolved_addr.push_back(host_ip);
} else if ( GUI::get_app_config()->get("allow_ip_resolve") == "1"){
Bonjour("octoprint")
.set_hostname(m_host)
.set_retries(10) // number of rounds of queries send
.set_timeout(1) // after each timeout, if there is any answer, the resolving will stop
.on_resolve([&ra = resolved_addr](const std::vector<BonjourReply>& replies) {
std::vector<boost::asio::ip::address> resolved_addr;
for each (const auto & rpl in replies) {
boost::asio::ip::address ip(rpl.ip);
ra.emplace_back(ip);
BOOST_LOG_TRIVIAL(info) << "Resolved IP address: " << rpl.ip;
}
})
.resolve_sync();
}
if (resolved_addr.empty()) {
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer failed to resolve hostname " << m_host << " into the IP address. Starting upload with system resolving.";
return false;//upload_inner_with_host(upload_data, prorgess_fn, error_fn);
}
return upload_inner(upload_data, prorgess_fn, error_fn, resolved_addr);
}
bool OctoPrint::upload_inner(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, const std::vector<boost::asio::ip::address>& resolved_addr) const
{
wxString error_message;
for each (const auto& ip in resolved_addr) {
// If test fails, test_msg_or_host_ip contains the error message.
// Otherwise on Windows it contains the resolved IP address of the host.
// Test_msg already contains resolved ip and will be cleared on start of test().
wxString test_msg_or_host_ip = GUI::from_u8(ip.to_string());
if (!test_with_resolved_ip(test_msg_or_host_ip)) {
error_message = test_msg_or_host_ip;
BOOST_LOG_TRIVIAL(info) << test_msg_or_host_ip;
continue;
}
const char* name = get_name();
const auto upload_filename = upload_data.upload_path.filename();
const auto upload_parent_path = upload_data.upload_path.parent_path();
std::string url = substitute_host(make_url("api/files/local"), ip.to_string());
bool result = true;
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
% name
% upload_data.source_path
% url
% upload_filename.string()
% upload_parent_path.string()
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
auto http = Http::post(std::move(url));
set_auth(http);
http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
error_fn(format_error(body, error, status));
result = false;
})
.on_progress([&](Http::Progress progress, bool& cancel) {
prorgess_fn(std::move(progress), cancel);
if (cancel) {
// Upload was canceled
BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled";
result = false;
}
})
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
#endif
.perform_sync();
if (result)
return true;
}
// todo: failed. Should we try again with host?
error_fn(std::move(error_message));
return false;
}
bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
{ {
const char* name = get_name(); const char* name = get_name();

View File

@ -8,6 +8,7 @@
#include "PrintHost.hpp" #include "PrintHost.hpp"
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
class boost::asio::ip::address;
namespace Slic3r { namespace Slic3r {
@ -35,6 +36,10 @@ public:
protected: protected:
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const; virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
virtual bool upload_inner(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, const std::vector<boost::asio::ip::address>& resolved_addr) const;
virtual bool test_with_resolved_ip(wxString& curl_msg) const;
virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const;
private: private:
std::string m_host; std::string m_host;