Release v0.6.63: automatic LAN transport fallback and connection help
This commit is contained in:
1 parent
3761b790ae
commit
5a2106899d
16 files changed
+376
-42
No files matched your search
+60
-18
@@ -1,6 +1,7 @@
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include "lan_channel.h"
|
||||
#include "lan_datagram.h"
|
||||
#include <bcrypt.h>
|
||||
#include <limits>
|
||||
|
||||
@@ -78,17 +79,18 @@ std::string inputSessionKey(const std::string &secret,const std::string &from,co
|
||||
}
|
||||
struct InputChannel::Impl
|
||||
{
|
||||
std::string local,peer,secret,nonce,state="Waiting for the paired computer...";
|
||||
std::string local,peer,secret,nonce,state="Waiting for the paired computer...",lastError;
|
||||
int edge;uint16_t port;SOCKET listener=INVALID_SOCKET,socket=INVALID_SOCKET;
|
||||
bool connecting{},authenticated{},helloRead{},managed{};
|
||||
uint64_t changed{},lastRead{},lastPing{},retry{};
|
||||
uint64_t changed{},lastRead{},lastPing{},retry{},waitingSince{};
|
||||
bool reverse{};
|
||||
Bytes incoming,outgoing;
|
||||
std::unique_ptr<InputCipher> tx,rx;
|
||||
Impl(std::string a,std::string b,std::string key,int side,uint16_t p,bool multiple):local(std::move(a)),peer(std::move(b)),secret(std::move(key)),edge(side),port(p),managed(multiple){}
|
||||
~Impl(){closeSocket(socket);closeSocket(listener);}
|
||||
void reset(uint64_t now,const std::string &reason)
|
||||
{
|
||||
closeSocket(socket);connecting=authenticated=helloRead=false;incoming.clear();outgoing.clear();tx.reset();rx.reset();++changed;retry=now+1500;state=reason;
|
||||
closeSocket(socket);connecting=authenticated=helloRead=false;incoming.clear();outgoing.clear();tx.reset();rx.reset();++changed;retry=now+1500;waitingSince=0;state=reason;lastError=reason;
|
||||
}
|
||||
void begin(uint64_t now)
|
||||
{
|
||||
@@ -101,6 +103,7 @@ struct InputChannel::Impl
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!waitingSince)waitingSince=now;
|
||||
if(socket==INVALID_SOCKET&&now<retry)return;
|
||||
if(!managed && listener==INVALID_SOCKET && local>peer)
|
||||
{
|
||||
@@ -111,14 +114,15 @@ struct InputChannel::Impl
|
||||
}
|
||||
if(socket==INVALID_SOCKET && address && now>=retry)
|
||||
{
|
||||
if(local<peer)
|
||||
if(local<peer||(managed&&now-waitingSince>=1500))
|
||||
{
|
||||
reverse=local>peer;
|
||||
socket=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(socket==INVALID_SOCKET||!nonblocking(socket))throw std::runtime_error("Cannot open input connection.");
|
||||
sockaddr_in a{};a.sin_family=AF_INET;a.sin_port=htons(port);a.sin_addr.s_addr=address;
|
||||
int result=connect(socket,(sockaddr*)&a,sizeof(a));
|
||||
if(result && WSAGetLastError()!=WSAEWOULDBLOCK)throw std::runtime_error("Paired computer is not accepting input connections.");
|
||||
connecting=result!=0;begin(now);
|
||||
if(connecting)state="Connecting to paired PC on TCP 45884...";
|
||||
if(connecting)state=reverse?"Trying reverse TCP connection...":"Trying direct TCP connection...";
|
||||
}
|
||||
else if(!managed)
|
||||
{
|
||||
@@ -185,7 +189,7 @@ struct InputChannel::Impl
|
||||
while(helloRead && incoming.size()>=36)
|
||||
{
|
||||
auto f=rx->decrypt(Bytes(incoming.begin(),incoming.begin()+36));incoming.erase(incoming.begin(),incoming.begin()+36);lastRead=now;
|
||||
if(!authenticated){if(f.type!=InputType::Ready)throw std::runtime_error("Input received before authentication.");authenticated=true;state="Input connected. Move through the configured screen edge.";}
|
||||
if(!authenticated){if(f.type!=InputType::Ready)throw std::runtime_error("Input received before authentication.");authenticated=true;lastError.clear();state="Input connected. Move through the configured screen edge.";}
|
||||
else if(f.type==InputType::Ready)throw std::runtime_error("Repeated input handshake.");
|
||||
else if(f.type!=InputType::Ping)received.push_back(f);
|
||||
}
|
||||
@@ -198,30 +202,44 @@ InputChannel::~InputChannel()=default;
|
||||
void InputChannel::poll(uint32_t ip,const std::vector<InputFrame>&out,std::vector<InputFrame>&in,uint64_t now){impl->poll(ip,out,in,now);}
|
||||
bool InputChannel::adopt(uintptr_t accepted,uint64_t now)
|
||||
{
|
||||
if(!impl->managed||impl->socket!=INVALID_SOCKET||!nonblocking(SOCKET(accepted)))return false;
|
||||
if(!impl->managed||impl->authenticated||!nonblocking(SOCKET(accepted)))return false;
|
||||
// Reverse connection wins a simultaneous connect race. Never replace an
|
||||
// authenticated channel or accept two sockets for the same input stream.
|
||||
if(impl->socket!=INVALID_SOCKET){
|
||||
if(impl->local>impl->peer||!impl->connecting)return false;
|
||||
impl->reset(now,"Accepting reverse TCP connection.");
|
||||
}
|
||||
impl->reverse=impl->local<impl->peer;
|
||||
impl->connecting=false;impl->begin(now);impl->socket=SOCKET(accepted);return true;
|
||||
}
|
||||
bool InputChannel::ready()const{return impl->authenticated;}
|
||||
uint64_t InputChannel::generation()const{return impl->changed;}
|
||||
std::string InputChannel::status()const{return impl->state;}
|
||||
std::string InputChannel::status()const{
|
||||
return impl->lastError.empty() ? impl->state : impl->lastError+" Retrying automatically.";
|
||||
}
|
||||
void InputChannel::disconnect(){impl->reset(GetTickCount64(),"Input disconnected; local control restored.");}
|
||||
std::string InputChannel::transport()const{return impl->reverse?"reverse TCP":"direct TCP";}
|
||||
struct InputHub::Impl
|
||||
{
|
||||
struct Link { InputEndpoint endpoint;std::unique_ptr<InputChannel> channel;uint64_t generation{},epoch{};std::string lastState; };
|
||||
struct Link { InputEndpoint endpoint;std::unique_ptr<InputChannel> channel;std::unique_ptr<InputDatagram> udp;uint64_t generation{},epoch{},since{},failedSince{};bool udpReady{};std::string lastState; };
|
||||
struct Pending {SOCKET socket;uint32_t address;uint64_t since;};
|
||||
std::string local;uint16_t port;SOCKET listener=INVALID_SOCKET;
|
||||
std::map<std::string,Link> links;std::vector<Pending> pending;uint64_t epoch{},retry{};
|
||||
std::string error;
|
||||
InputHub::SendDatagram sendDatagram;
|
||||
uint64_t currentTime{};
|
||||
Impl(std::string id,uint16_t p):local(std::move(id)),port(p){}
|
||||
~Impl(){closeSocket(listener);for(auto &p:pending)closeSocket(p.socket);}
|
||||
void poll(const std::vector<InputEndpoint> &endpoints,const std::vector<RoutedInput> &out,std::vector<RoutedInput> &in,uint64_t now)
|
||||
{
|
||||
currentTime=now;
|
||||
for(auto it=links.begin();it!=links.end();){
|
||||
auto match=std::find_if(endpoints.begin(),endpoints.end(),[&](auto &e){return e.id==it->first&&e.key==it->second.endpoint.key&&e.port==it->second.endpoint.port;});
|
||||
if(match==endpoints.end())it=links.erase(it);else ++it;
|
||||
}
|
||||
for(auto &e:endpoints)if(e.id!=local&&!links.contains(e.id)){
|
||||
Link link;link.endpoint=e;link.channel=std::make_unique<InputChannel>(local,e.id,e.key,0,e.port,true);link.epoch=++epoch;links.emplace(e.id,std::move(link));
|
||||
Link link;link.endpoint=e;link.channel=std::make_unique<InputChannel>(local,e.id,e.key,0,e.port,true);
|
||||
link.udp=std::make_unique<InputDatagram>(local,e.id,e.key);link.since=link.failedSince=now;link.epoch=++epoch;links.emplace(e.id,std::move(link));
|
||||
}
|
||||
// Discovery is a hint, not the lifetime of authenticated input sessions.
|
||||
// Keep the last usable address across missed announcements. A new address
|
||||
@@ -231,7 +249,7 @@ struct InputHub::Impl
|
||||
link.channel->disconnect();link.endpoint.address=e.address;
|
||||
}
|
||||
}
|
||||
bool listenNeeded=std::any_of(endpoints.begin(),endpoints.end(),[&](auto &e){return e.id<local;});
|
||||
bool listenNeeded=!links.empty();
|
||||
if(!listenNeeded){closeSocket(listener);for(auto &p:pending)closeSocket(p.socket);pending.clear();}
|
||||
if(listenNeeded&&listener==INVALID_SOCKET&&now>=retry){
|
||||
listener=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);BOOL yes=TRUE;sockaddr_in a{};a.sin_family=AF_INET;a.sin_port=htons(port);
|
||||
@@ -253,7 +271,7 @@ struct InputHub::Impl
|
||||
else if(count>=int(length+4)){
|
||||
done=true;
|
||||
try{auto hello=Json::parse(bytes+4,bytes+4+length);auto id=hello.value("id","");auto link=links.find(id);
|
||||
if(id<local&&hello.value("to","")==local&&hello.value("version",0)==2&&link!=links.end()&&link->second.channel->adopt(uintptr_t(it->socket),now))it->socket=INVALID_SOCKET;
|
||||
if(hello.value("app","")=="KSwitcher-input"&&hello.value("to","")==local&&hello.value("version",0)==2&&link!=links.end()&&!link->second.udp->active()&&link->second.channel->adopt(uintptr_t(it->socket),now))it->socket=INVALID_SOCKET;
|
||||
}catch(const std::exception &){}
|
||||
}
|
||||
}
|
||||
@@ -261,8 +279,16 @@ struct InputHub::Impl
|
||||
}
|
||||
for(auto &[id,link]:links){
|
||||
std::vector<InputFrame> frames,received;for(auto &f:out)if(f.peer==id&&(!f.epoch||f.epoch==link.epoch))enqueueInput(frames,f.frame);
|
||||
link.channel->poll(link.endpoint.address,frames,received,now);
|
||||
auto state=link.channel->status();if(state!=link.lastState){link.lastState=state;log(L"LAN input peer "+wide(id.substr(0,8))+L": "+wide(state));}
|
||||
if(sendDatagram&&!link.channel->ready()&&!link.udp->active()&&local<id&&now-link.since>=5000){link.udp->start(now);link.channel->disconnect();frames.clear();}
|
||||
if(link.udp->active()){
|
||||
link.udp->poll(frames,received,now,sendDatagram);
|
||||
if(!link.udp->active()){link.since=now;link.channel->disconnect();received.clear();}
|
||||
}else link.channel->poll(link.endpoint.address,frames,received,now);
|
||||
if(link.udpReady!=link.udp->ready()){link.udpReady=link.udp->ready();link.epoch=++epoch;}
|
||||
bool ready=link.channel->ready()||link.udp->ready();
|
||||
if(ready)link.failedSince=link.since=now;
|
||||
auto state=link.udp->ready()?"Input connected via encrypted UDP.":link.udp->active()?"Trying encrypted UDP over discovery...":link.channel->ready()?"Input connected via "+link.channel->transport()+".":link.channel->status();
|
||||
if(state!=link.lastState){link.lastState=state;log(L"LAN input peer "+wide(id.substr(0,8))+L": "+wide(state));}
|
||||
if(link.generation!=link.channel->generation()){link.generation=link.channel->generation();link.epoch=++epoch;}
|
||||
for(auto &f:received)in.push_back({id,f,link.epoch});
|
||||
}
|
||||
@@ -271,12 +297,28 @@ struct InputHub::Impl
|
||||
InputHub::InputHub(std::string id,uint16_t port):impl(std::make_unique<Impl>(std::move(id),port)){}
|
||||
InputHub::~InputHub()=default;
|
||||
void InputHub::poll(const std::vector<InputEndpoint> &peers,const std::vector<RoutedInput> &out,std::vector<RoutedInput> &in,uint64_t now){impl->poll(peers,out,in,now);}
|
||||
std::map<std::string,uint64_t> InputHub::ready()const{std::map<std::string,uint64_t> result;for(auto &[id,l]:impl->links)if(l.channel->ready())result[id]=l.epoch;return result;}
|
||||
std::map<std::string,uint64_t> InputHub::ready()const{std::map<std::string,uint64_t> result;for(auto &[id,l]:impl->links)if(l.channel->ready()||l.udp->ready())result[id]=l.epoch;return result;}
|
||||
std::string InputHub::status()const{
|
||||
auto count=ready().size();if(count)return "Input connected to "+std::to_string(count)+" computer(s). Move through a neighboring screen edge.";
|
||||
auto count=ready().size();if(count==impl->links.size()&&count){
|
||||
std::string routes;for(auto &[id,l]:impl->links){if(!routes.empty())routes+=", ";routes+=id.substr(0,8)+": "+(l.udp->ready()?"encrypted UDP":l.channel->transport());}
|
||||
return "Input connected ("+routes+"). Move through a neighboring screen edge.";
|
||||
}
|
||||
if(needsHelp())return "Automatic connection attempts have not succeeded for every PC. Enable input on both PCs. Open Connection help for firewall commands.";
|
||||
if(!impl->error.empty())return impl->error;
|
||||
for(auto &[id,l]:impl->links)if(!l.channel->status().empty())return l.channel->status();
|
||||
for(auto &[id,l]:impl->links)if(!l.channel->ready()&&!l.udp->ready())return l.lastState;
|
||||
return "Waiting for paired computers...";
|
||||
}
|
||||
void InputHub::disconnect(){for(auto &[id,l]:impl->links)l.channel->disconnect();}
|
||||
void InputHub::disconnect(){for(auto &[id,l]:impl->links){l.channel->disconnect();l.udp->reset();l.since=impl->currentTime;}}
|
||||
void InputHub::datagrams(SendDatagram send){impl->sendDatagram=std::move(send);}
|
||||
void InputHub::receiveDatagram(const Json &j,uint64_t now){
|
||||
if(!impl->sendDatagram)return;
|
||||
auto it=impl->links.find(j.value("id",""));if(it==impl->links.end()||it->second.channel->ready())return;
|
||||
auto &link=it->second;bool active=link.udp->active();
|
||||
try{link.udp->receive(j,now,impl->sendDatagram);}catch(const std::exception &){return;}
|
||||
if(!active&&link.udp->active())link.channel->disconnect();
|
||||
}
|
||||
bool InputHub::needsHelp()const{
|
||||
for(auto &[id,l]:impl->links)if(!l.channel->ready()&&!l.udp->ready()&&impl->currentTime-l.failedSince>=15000)return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user