- add MeshPort peer-to-peer overlay networking SDK, C++ integration, and Python bindings - add item actions quick use/split/delete/lock overlay window (mt_item_actions) - add shop name range and NPC name visibility settings to system display options - support battlepass ticket activation and month validation in mt_battlepass - support boss tracking teleport destination button in mt_bosses - support asynchronous account registration cancellation and pending login cancellation - update AVA2 item conversion for attr lock scrolls and battlepass tickets - update .gitignore for local build outputs and caches
104 lines
7.1 KiB
C++
104 lines
7.1 KiB
C++
#include "transport.h"
|
|
#include <arpa/inet.h>
|
|
#include <sys/socket.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#define CHECK(x) do { if (!(x)) { std::fprintf(stderr, "line %d: %s\n", __LINE__, #x); std::abort(); } } while (0)
|
|
using namespace mt::meshport;
|
|
uint32_t ip(const char* text) { in_addr addr{}; CHECK(inet_pton(AF_INET,text,&addr)==1); return addr.s_addr; }
|
|
void transfer(int fd, void* p, size_t size, bool send) {
|
|
auto* bytes = static_cast<unsigned char*>(p);
|
|
for (size_t offset=0; offset<size;) {
|
|
auto count = send ? ::send(fd,bytes+offset,1,0) : ::recv(fd,bytes+offset,size-offset,0);
|
|
CHECK(count>0); offset += count;
|
|
}
|
|
}
|
|
void socks(bool reject) {
|
|
int listener=::socket(AF_INET,SOCK_STREAM,0); CHECK(listener>=0);
|
|
sockaddr_in local{};local.sin_family=AF_INET;local.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
|
|
CHECK(::bind(listener,reinterpret_cast<sockaddr*>(&local),sizeof(local))==0);
|
|
CHECK(::listen(listener,1)==0);socklen_t len=sizeof(local);
|
|
CHECK(getsockname(listener,reinterpret_cast<sockaddr*>(&local),&len)==0);
|
|
std::thread peer([&] {
|
|
int fd=::accept(listener,nullptr,nullptr);CHECK(fd>=0);
|
|
timeval timeout{3,0};setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
|
|
unsigned char greeting[3];transfer(fd,greeting,3,false);CHECK(greeting[0]==5 && greeting[1]==1 && greeting[2]==0);
|
|
unsigned char auth[2]={5,0};transfer(fd,auth,2,true);
|
|
unsigned char request[10];transfer(fd,request,10,false);
|
|
CHECK(request[0]==5 && request[1]==1 && request[3]==1);
|
|
CHECK(std::memcmp(request+4,"\x0a\xfd\x55\x02",4)==0);
|
|
CHECK(request[8]==0x32 && request[9]==0xc8); // port 13000
|
|
unsigned char response[10]={5,static_cast<unsigned char>(reject?5:0),0,1,0,0,0,0,0,0};
|
|
transfer(fd,response,10,true);
|
|
if (!reject) { char payload[4]={'g','a','m','e'};transfer(fd,payload,4,true); }
|
|
::close(fd);
|
|
});
|
|
set_routes("127.0.0.1:"+std::to_string(ntohs(local.sin_port)),{"10.253.85.0/24"});
|
|
int fd=::socket(AF_INET,SOCK_STREAM,0);CHECK(fd>=0);fcntl(fd,F_SETFL,O_NONBLOCK);
|
|
sockaddr_in target{};target.sin_family=AF_INET;target.sin_addr.s_addr=ip("10.253.85.2");target.sin_port=htons(13000);
|
|
int owner;CHECK(connect_socket(fd,target,&owner)==-1);CHECK(pending(fd));CHECK(pending_owner(&owner));
|
|
int result=0;
|
|
const auto deadline=std::chrono::steady_clock::now()+std::chrono::seconds(3);
|
|
while (!result && std::chrono::steady_clock::now()<deadline) {result=poll_socket(fd);std::this_thread::sleep_for(std::chrono::milliseconds(1));}
|
|
CHECK(result==(reject?-1:1));
|
|
if (!reject) { fcntl(fd,F_SETFL,0); char payload[4];transfer(fd,payload,4,false);CHECK(std::memcmp(payload,"game",4)==0); }
|
|
forget_socket(fd);CHECK(!pending(fd));CHECK(!pending_owner(&owner));::close(fd);peer.join();::close(listener);set_routes("",{});
|
|
}
|
|
void udp_socks() {
|
|
int listener=::socket(AF_INET,SOCK_STREAM,0);CHECK(listener>=0);
|
|
auto local=sockaddr_in{};local.sin_family=AF_INET;local.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
|
|
CHECK(::bind(listener,reinterpret_cast<sockaddr*>(&local),sizeof(local))==0);CHECK(::listen(listener,1)==0);
|
|
socklen_t len=sizeof(local);CHECK(getsockname(listener,reinterpret_cast<sockaddr*>(&local),&len)==0);
|
|
int relay=::socket(AF_INET,SOCK_DGRAM,0);CHECK(relay>=0);auto relay_address=local;relay_address.sin_port=0;
|
|
CHECK(::bind(relay,reinterpret_cast<sockaddr*>(&relay_address),sizeof(relay_address))==0);
|
|
len=sizeof(relay_address);CHECK(getsockname(relay,reinterpret_cast<sockaddr*>(&relay_address),&len)==0);
|
|
std::thread peer([&]{
|
|
int control=::accept(listener,nullptr,nullptr);CHECK(control>=0);timeval timeout{4,0};
|
|
setsockopt(control,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));setsockopt(relay,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
|
|
unsigned char greeting[3];transfer(control,greeting,3,false);unsigned char auth[2]={5,0};transfer(control,auth,2,true);
|
|
unsigned char request[10];transfer(control,request,10,false);CHECK(request[1]==3);
|
|
unsigned char response[10]={5,0,0,1,127,0,0,1,0,0};std::memcpy(response+8,&relay_address.sin_port,2);transfer(control,response,10,true);
|
|
unsigned char frame[128];sockaddr_in source{};socklen_t size=sizeof(source);
|
|
auto count=::recvfrom(relay,frame,sizeof(frame),0,reinterpret_cast<sockaddr*>(&source),&size);
|
|
CHECK(count==14);CHECK(std::memcmp(frame+10,"ping",4)==0);
|
|
CHECK(::sendto(relay,frame,count,0,reinterpret_cast<sockaddr*>(&source),size)==count);
|
|
char close_byte;::recv(control,&close_byte,1,0);::close(control);
|
|
});
|
|
set_routes("127.0.0.1:"+std::to_string(ntohs(local.sin_port)),{"10.253.85.0/24"});
|
|
int fd=::socket(AF_INET,SOCK_DGRAM,0);CHECK(fd>=0);fcntl(fd,F_SETFL,O_NONBLOCK);
|
|
auto target=sockaddr_in{};target.sin_family=AF_INET;target.sin_addr.s_addr=ip("10.253.85.2");target.sin_port=htons(13000);
|
|
CHECK(send_datagram(fd,"ping",4,0,target)==4);
|
|
const auto deadline=std::chrono::steady_clock::now()+std::chrono::seconds(4);
|
|
while(!datagram_ready(fd)&&std::chrono::steady_clock::now()<deadline)std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
CHECK(datagram_ready(fd));sockaddr_in source{};char payload[4];
|
|
CHECK(recv_datagram(fd,payload,2,MSG_PEEK,&source)==2);CHECK(source.sin_addr.s_addr==target.sin_addr.s_addr&&source.sin_port==target.sin_port);
|
|
CHECK(recv_datagram(fd,payload,4,0,&source)==4);CHECK(std::memcmp(payload,"ping",4)==0);
|
|
forget_socket(fd);::close(fd);peer.join();::close(listener);::close(relay);set_routes("",{});
|
|
}
|
|
int main() {
|
|
CHECK(in_subnet(ip("192.168.21.203"),ip("192.168.21.5"),ip("255.255.255.0")));
|
|
CHECK(!in_subnet(ip("192.168.20.203"),ip("192.168.21.5"),ip("255.255.255.0")));
|
|
CHECK(in_subnet(ip("192.168.20.203"),ip("192.168.21.5"),ip("255.255.254.0")));
|
|
CHECK(!in_subnet(ip("192.168.21.203"),ip("192.168.21.5"),0));
|
|
CHECK(same_subnet("127.0.0.1"));CHECK(!same_subnet("invalid"));
|
|
CHECK(contains("10.253.85.0/24","10.253.85.2"));CHECK(!contains("10.253.85.0/24","10.253.86.2"));
|
|
CHECK(contains("10.253.85.2/32","10.253.85.2"));CHECK(!contains("10.0.0.0/0","10.1.2.3"));
|
|
CHECK(!contains("10.0.0.0/99999999","10.1.2.3"));
|
|
socks(false);socks(true);udp_socks();
|
|
configure("invalid saved profile");
|
|
int failed=::socket(AF_INET,SOCK_STREAM,0);CHECK(failed>=0);fcntl(failed,F_SETFL,O_NONBLOCK);
|
|
sockaddr_in absent{};absent.sin_family=AF_INET;absent.sin_addr.s_addr=ip("10.253.85.2");absent.sin_port=htons(13000);
|
|
CHECK(connect_socket(failed,absent)==-1);
|
|
int state=0;auto deadline=std::chrono::steady_clock::now()+std::chrono::seconds(5);
|
|
while(!state&&std::chrono::steady_clock::now()<deadline){state=poll_socket(failed);std::this_thread::sleep_for(std::chrono::milliseconds(1));}
|
|
CHECK(state==-1);socklen_t length=sizeof(absent);CHECK(getpeername(failed,reinterpret_cast<sockaddr*>(&absent),&length)<0);
|
|
forget_socket(failed);::close(failed);
|
|
shutdown();std::puts("MeshPort subnet, TCP/UDP SOCKS, peek/source preservation and failure without direct fallback passed");
|
|
}
|