2V1-a: port the EterLib network layer and the Crypto++ packet cipher
NetStream/NetAddress/NetDevice/NetPacketHeaderMap and EterBase/cipher.cpp are mechanical copies. The winsock shim maps Winsock onto BSD sockets and keeps the Winsock semantics CNetworkStream relies on: select ignores nfds, a socket whose connect failed is not writable (40250 times out instead), EINPROGRESS reads as WSAEWOULDBLOCK, and SIGPIPE is ignored. port.net covers loopback connect, send/recv, peer close, the refused connect timeout and the client/server key agreement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
afe63bfa61
commit
cd3b8b506f
@@ -174,6 +174,10 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
|
||||
add_test(NAME port.text COMMAND $<TARGET_FILE:port_text_test>)
|
||||
set_tests_properties(port.text PROPERTIES SKIP_RETURN_CODE 77)
|
||||
|
||||
add_executable(port_net_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_net_test.cpp)
|
||||
target_link_libraries(port_net_test PRIVATE port_platform)
|
||||
add_test(NAME port.net COMMAND $<TARGET_FILE:port_net_test>)
|
||||
|
||||
if(TARGET mtpython)
|
||||
add_executable(port_python_launcher_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_python_launcher_test.cpp)
|
||||
target_link_libraries(port_python_launcher_test PRIVATE port_platform)
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
#include "StdAfx.h"
|
||||
|
||||
#include "cipher.h"
|
||||
|
||||
#ifdef _IMPROVED_PACKET_ENCRYPTION_
|
||||
|
||||
//#pragma warning(push)
|
||||
//#pragma warning(disable: 4100 4127 4189 4231 4512 4706)
|
||||
|
||||
#include <cryptopp/modes.h>
|
||||
#include <cryptopp/nbtheory.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
|
||||
// Diffie-Hellman key agreement
|
||||
#include <cryptopp/dh.h>
|
||||
#include <cryptopp/dh2.h>
|
||||
|
||||
// AES winner and candidates
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/cast.h>
|
||||
#include <cryptopp/rc6.h>
|
||||
#include <cryptopp/mars.h>
|
||||
#include <cryptopp/serpent.h>
|
||||
#include <cryptopp/twofish.h>
|
||||
// Other block ciphers
|
||||
#include <cryptopp/blowfish.h>
|
||||
#include <cryptopp/camellia.h>
|
||||
#include <cryptopp/des.h>
|
||||
#include <cryptopp/idea.h>
|
||||
#include <cryptopp/rc5.h>
|
||||
#include <cryptopp/seed.h>
|
||||
#include <cryptopp/shacal2.h>
|
||||
#include <cryptopp/skipjack.h>
|
||||
#include <cryptopp/tea.h>
|
||||
|
||||
#ifdef __THEMIDA__
|
||||
#include <ThemidaSDK.h>
|
||||
#endif
|
||||
|
||||
#include "Debug.h"
|
||||
|
||||
|
||||
using namespace CryptoPP;
|
||||
|
||||
// Block cipher algorithm selector abstract base class.
|
||||
struct BlockCipherAlgorithm {
|
||||
enum {
|
||||
kDefault, // to give more chances to default algorithm
|
||||
// AES winner and candidates
|
||||
// kAES, // Rijndael
|
||||
kRC6,
|
||||
kMARS,
|
||||
kTwofish,
|
||||
kSerpent,
|
||||
kCAST256,
|
||||
// Other block ciphers
|
||||
kIDEA,
|
||||
k3DES, // DES-EDE2
|
||||
kCamellia,
|
||||
kSEED,
|
||||
kRC5,
|
||||
kBlowfish,
|
||||
kTEA,
|
||||
//kSKIPJACK,
|
||||
kSHACAL2,
|
||||
// End sentinel
|
||||
kMaxAlgorithms
|
||||
};
|
||||
|
||||
BlockCipherAlgorithm() {}
|
||||
virtual ~BlockCipherAlgorithm() {}
|
||||
|
||||
static BlockCipherAlgorithm* Pick(int hint);
|
||||
|
||||
virtual int GetBlockSize() const = 0;
|
||||
virtual int GetDefaultKeyLength() const = 0;
|
||||
virtual int GetIVLength() const = 0;
|
||||
|
||||
virtual SymmetricCipher* CreateEncoder(const CryptoPP::byte* key, size_t keylen,
|
||||
const CryptoPP::byte* iv) const = 0;
|
||||
virtual SymmetricCipher* CreateDecoder(const CryptoPP::byte* key, size_t keylen,
|
||||
const CryptoPP::byte* iv) const = 0;
|
||||
};
|
||||
|
||||
// Block cipher (with CTR mode) algorithm selector template class.
|
||||
template<class T>
|
||||
struct BlockCipherDetail : public BlockCipherAlgorithm {
|
||||
BlockCipherDetail() {}
|
||||
virtual ~BlockCipherDetail() {}
|
||||
|
||||
virtual int GetBlockSize() const { return T::BLOCKSIZE; }
|
||||
virtual int GetDefaultKeyLength() const { return T::DEFAULT_KEYLENGTH; }
|
||||
virtual int GetIVLength() const { return T::IV_LENGTH; }
|
||||
|
||||
virtual SymmetricCipher* CreateEncoder(const CryptoPP::byte* key, size_t keylen,
|
||||
const CryptoPP::byte* iv) const
|
||||
{
|
||||
return new typename CTR_Mode<T>::Encryption(key, keylen, iv);
|
||||
}
|
||||
virtual SymmetricCipher* CreateDecoder(const CryptoPP::byte* key, size_t keylen,
|
||||
const CryptoPP::byte* iv) const
|
||||
{
|
||||
return new typename CTR_Mode<T>::Decryption(key, keylen, iv);
|
||||
}
|
||||
};
|
||||
|
||||
// Key agreement scheme abstract class.
|
||||
class KeyAgreement {
|
||||
public:
|
||||
KeyAgreement() {}
|
||||
virtual ~KeyAgreement() {}
|
||||
|
||||
virtual size_t Prepare(void* buffer, size_t* length) = 0;
|
||||
virtual bool Agree(size_t agreed_length, const void* buffer, size_t length) = 0;
|
||||
|
||||
const SecByteBlock& shared() const { return shared_; }
|
||||
|
||||
protected:
|
||||
SecByteBlock shared_;
|
||||
};
|
||||
|
||||
// Crypto++ Unified Diffie-Hellman key agreement scheme implementation.
|
||||
class DH2KeyAgreement : public KeyAgreement {
|
||||
public:
|
||||
DH2KeyAgreement();
|
||||
virtual ~DH2KeyAgreement();
|
||||
|
||||
virtual size_t Prepare(void* buffer, size_t* length);
|
||||
virtual bool Agree(size_t agreed_length, const void* buffer, size_t length);
|
||||
|
||||
private:
|
||||
DH dh_;
|
||||
DH2 dh2_;
|
||||
SecByteBlock spriv_key_;
|
||||
SecByteBlock epriv_key_;
|
||||
};
|
||||
|
||||
Cipher::Cipher()
|
||||
: activated_(false), encoder_(NULL), decoder_(NULL), key_agreement_(NULL) {
|
||||
}
|
||||
|
||||
Cipher::~Cipher() {
|
||||
if (activated_) {
|
||||
CleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
void Cipher::CleanUp() {
|
||||
if (encoder_ != NULL) {
|
||||
delete encoder_;
|
||||
encoder_ = NULL;
|
||||
}
|
||||
if (decoder_ != NULL) {
|
||||
delete decoder_;
|
||||
decoder_ = NULL;
|
||||
}
|
||||
if (key_agreement_ != NULL) {
|
||||
delete key_agreement_;
|
||||
key_agreement_ = NULL;
|
||||
}
|
||||
activated_ = false;
|
||||
}
|
||||
|
||||
size_t Cipher::Prepare(void* buffer, size_t* length) {
|
||||
#ifdef __THEMIDA__
|
||||
VM_START
|
||||
#endif
|
||||
|
||||
assert(key_agreement_ == NULL);
|
||||
key_agreement_ = new DH2KeyAgreement();
|
||||
assert(key_agreement_ != NULL);
|
||||
size_t agreed_length = key_agreement_->Prepare(buffer, length);
|
||||
if (agreed_length == 0) {
|
||||
delete key_agreement_;
|
||||
key_agreement_ = NULL;
|
||||
}
|
||||
#ifdef __THEMIDA__
|
||||
VM_END
|
||||
#endif
|
||||
|
||||
return agreed_length;
|
||||
}
|
||||
|
||||
bool Cipher::Activate(bool polarity, size_t agreed_length,
|
||||
const void* buffer, size_t length) {
|
||||
#ifdef __THEMIDA__
|
||||
VM_START
|
||||
#endif
|
||||
|
||||
assert(activated_ == false);
|
||||
assert(key_agreement_ != NULL);
|
||||
bool result = false;
|
||||
if (key_agreement_->Agree(agreed_length, buffer, length)) {
|
||||
result = SetUp(polarity);
|
||||
}
|
||||
delete key_agreement_;
|
||||
key_agreement_ = NULL;
|
||||
#ifdef __THEMIDA__
|
||||
VM_END
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Cipher::SetUp(bool polarity) {
|
||||
#ifdef __THEMIDA__
|
||||
VM_START
|
||||
#endif
|
||||
|
||||
assert(key_agreement_ != NULL);
|
||||
const SecByteBlock& shared = key_agreement_->shared();
|
||||
|
||||
// Pick a block cipher algorithm
|
||||
|
||||
if (shared.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int hint_0 = shared.BytePtr()[*(shared.BytePtr()) % shared.size()];
|
||||
int hint_1 = shared.BytePtr()[*(shared.BytePtr() + 1) % shared.size()];
|
||||
BlockCipherAlgorithm* detail_0 = BlockCipherAlgorithm::Pick(hint_0);
|
||||
BlockCipherAlgorithm* detail_1 = BlockCipherAlgorithm::Pick(hint_1);
|
||||
assert(detail_0 != NULL);
|
||||
assert(detail_1 != NULL);
|
||||
std::unique_ptr<BlockCipherAlgorithm> algorithm_0(detail_0);
|
||||
std::unique_ptr<BlockCipherAlgorithm> algorithm_1(detail_1);
|
||||
|
||||
const size_t key_length_0 = algorithm_0->GetDefaultKeyLength();
|
||||
const size_t iv_length_0 = algorithm_0->GetBlockSize();
|
||||
|
||||
if (shared.size() < key_length_0 || shared.size() < iv_length_0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t key_length_1 = algorithm_1->GetDefaultKeyLength();
|
||||
const size_t iv_length_1 = algorithm_1->GetBlockSize();
|
||||
|
||||
if (shared.size() < key_length_1 || shared.size() < iv_length_1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pick encryption keys and initial vectors
|
||||
|
||||
SecByteBlock key_0(key_length_0), iv_0(iv_length_0);
|
||||
SecByteBlock key_1(key_length_1), iv_1(iv_length_1);
|
||||
|
||||
size_t offset;
|
||||
|
||||
key_0.Assign(shared, key_length_0);
|
||||
offset = key_length_0;
|
||||
offset = min(key_length_0, shared.size() - key_length_1);
|
||||
key_1.Assign(shared.BytePtr() + offset, key_length_1);
|
||||
|
||||
offset = shared.size() - iv_length_0;
|
||||
iv_0.Assign(shared.BytePtr() + offset, iv_length_0);
|
||||
offset = (offset < iv_length_1 ? 0 : offset - iv_length_1);
|
||||
iv_1.Assign(shared.BytePtr() + offset, iv_length_1);
|
||||
|
||||
// Create encryption/decryption objects
|
||||
|
||||
if (polarity) {
|
||||
encoder_ = algorithm_1->CreateEncoder(key_1, key_1.size(), iv_1);
|
||||
decoder_ = algorithm_0->CreateDecoder(key_0, key_0.size(), iv_0);
|
||||
} else {
|
||||
encoder_ = algorithm_0->CreateEncoder(key_0, key_0.size(), iv_0);
|
||||
decoder_ = algorithm_1->CreateDecoder(key_1, key_1.size(), iv_1);
|
||||
}
|
||||
|
||||
assert(encoder_ != NULL);
|
||||
assert(decoder_ != NULL);
|
||||
#ifdef __THEMIDA__
|
||||
VM_END
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockCipherAlgorithm* BlockCipherAlgorithm::Pick(int hint) {
|
||||
BlockCipherAlgorithm* detail;
|
||||
int selector = hint % kMaxAlgorithms;
|
||||
switch (selector) {
|
||||
//case kAES:
|
||||
// detail = new BlockCipherDetail<AES>();
|
||||
break;
|
||||
case kRC6:
|
||||
detail = new BlockCipherDetail<RC6>();
|
||||
break;
|
||||
case kMARS:
|
||||
detail = new BlockCipherDetail<MARS>();
|
||||
break;
|
||||
case kTwofish:
|
||||
detail = new BlockCipherDetail<Twofish>();
|
||||
break;
|
||||
case kSerpent:
|
||||
detail = new BlockCipherDetail<Serpent>();
|
||||
break;
|
||||
case kCAST256:
|
||||
detail = new BlockCipherDetail<CAST256>();
|
||||
break;
|
||||
case kIDEA:
|
||||
detail = new BlockCipherDetail<IDEA>();
|
||||
break;
|
||||
case k3DES:
|
||||
detail = new BlockCipherDetail<DES_EDE2>();
|
||||
break;
|
||||
case kCamellia:
|
||||
detail = new BlockCipherDetail<Camellia>();
|
||||
break;
|
||||
case kSEED:
|
||||
detail = new BlockCipherDetail<SEED>();
|
||||
break;
|
||||
case kRC5:
|
||||
detail = new BlockCipherDetail<RC5>();
|
||||
break;
|
||||
case kBlowfish:
|
||||
detail = new BlockCipherDetail<Blowfish>();
|
||||
break;
|
||||
case kTEA:
|
||||
detail = new BlockCipherDetail<TEA>();
|
||||
break;
|
||||
// case kSKIPJACK:
|
||||
// detail = new BlockCipherDetail<SKIPJACK>();
|
||||
// break;
|
||||
case kSHACAL2:
|
||||
detail = new BlockCipherDetail<SHACAL2>();
|
||||
break;
|
||||
case kDefault:
|
||||
default:
|
||||
detail = new BlockCipherDetail<Twofish>(); // default algorithm
|
||||
break;
|
||||
}
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
DH2KeyAgreement::DH2KeyAgreement() : dh_(), dh2_(dh_) {
|
||||
}
|
||||
|
||||
DH2KeyAgreement::~DH2KeyAgreement() {
|
||||
}
|
||||
|
||||
size_t DH2KeyAgreement::Prepare(void* buffer, size_t* length) {
|
||||
#ifdef __THEMIDA__
|
||||
VM_START
|
||||
#endif
|
||||
|
||||
// RFC 5114, 1024-bit MODP Group with 160-bit Prime Order Subgroup
|
||||
// http://tools.ietf.org/html/rfc5114#section-2.1
|
||||
Integer p("0xB10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6"
|
||||
"9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0"
|
||||
"13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70"
|
||||
"98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0"
|
||||
"A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708"
|
||||
"DF1FB2BC2E4A4371");
|
||||
|
||||
Integer g("0xA4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F"
|
||||
"D6406CFF14266D31266FEA1E5C41564B777E690F5504F213"
|
||||
"160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1"
|
||||
"909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A"
|
||||
"D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24"
|
||||
"855E6EEB22B3B2E5");
|
||||
|
||||
Integer q("0xF518AA8781A8DF278ABA4E7D64B7CB9D49462353");
|
||||
|
||||
// Schnorr Group primes are of the form p = rq + 1, p and q prime. They
|
||||
// provide a subgroup order. In the case of 1024-bit MODP Group, the
|
||||
// security level is 80 bits (based on the 160-bit prime order subgroup).
|
||||
|
||||
// For a compare/contrast of using the maximum security level, see
|
||||
// dh-unified.zip. Also see http://www.cryptopp.com/wiki/Diffie-Hellman
|
||||
// and http://www.cryptopp.com/wiki/Security_level .
|
||||
|
||||
AutoSeededRandomPool rnd;
|
||||
|
||||
dh_.AccessGroupParameters().Initialize(p, q, g);
|
||||
|
||||
if(!dh_.GetGroupParameters().ValidateGroup(rnd, 3)) {
|
||||
// Failed to validate prime and generator
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
p = dh_.GetGroupParameters().GetModulus();
|
||||
q = dh_.GetGroupParameters().GetSubgroupOrder();
|
||||
g = dh_.GetGroupParameters().GetGenerator();
|
||||
|
||||
// http://groups.google.com/group/sci.crypt/browse_thread/thread/7dc7eeb04a09f0ce
|
||||
Integer v = ModularExponentiation(g, q, p);
|
||||
|
||||
if(v != Integer::One()) {
|
||||
// Failed to verify order of the subgroup
|
||||
return 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
spriv_key_.New(dh2_.StaticPrivateKeyLength());
|
||||
epriv_key_.New(dh2_.EphemeralPrivateKeyLength());
|
||||
SecByteBlock spub_key(dh2_.StaticPublicKeyLength());
|
||||
SecByteBlock epub_key(dh2_.EphemeralPublicKeyLength());
|
||||
|
||||
dh2_.GenerateStaticKeyPair(rnd, spriv_key_, spub_key);
|
||||
dh2_.GenerateEphemeralKeyPair(rnd, epriv_key_, epub_key);
|
||||
|
||||
// Prepare key agreement data
|
||||
const size_t spub_key_length = spub_key.size();
|
||||
const size_t epub_key_length = epub_key.size();
|
||||
const size_t data_length = spub_key_length + epub_key_length;
|
||||
|
||||
if (*length < data_length) {
|
||||
// Not enough data buffer length b-l-a-c-k
|
||||
return 0;
|
||||
}
|
||||
|
||||
*length = data_length;
|
||||
CryptoPP::byte* buf = (CryptoPP::byte*)buffer;
|
||||
memcpy(buf, spub_key.BytePtr(), spub_key_length);
|
||||
memcpy(buf + spub_key_length, epub_key.BytePtr(), epub_key_length);
|
||||
|
||||
#ifdef __THEMIDA__
|
||||
VM_END
|
||||
#endif
|
||||
|
||||
return dh2_.AgreedValueLength();
|
||||
}
|
||||
|
||||
bool DH2KeyAgreement::Agree(size_t agreed_length, const void* buffer, size_t length) {
|
||||
if (agreed_length != dh2_.AgreedValueLength()) {
|
||||
// Shared secret size mismatch
|
||||
return false;
|
||||
}
|
||||
const size_t spub_key_length = dh2_.StaticPublicKeyLength();
|
||||
const size_t epub_key_length = dh2_.EphemeralPublicKeyLength();
|
||||
if (length != (spub_key_length + epub_key_length)) {
|
||||
// Wrong data length
|
||||
return false;
|
||||
}
|
||||
shared_.New(dh2_.AgreedValueLength());
|
||||
const CryptoPP::byte* buf = (const CryptoPP::byte*)buffer;
|
||||
if (!dh2_.Agree(shared_, spriv_key_, epriv_key_, buf, buf + spub_key_length)) {
|
||||
// Failed to reach shared secret
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "StdAfx.h"
|
||||
#include "NetAddress.h"
|
||||
|
||||
#ifndef VC_EXTRALEAN
|
||||
|
||||
bool CNetworkAddress::GetHostName(char* szName, int size)
|
||||
{
|
||||
if (gethostname(szName, size)==SOCKET_ERROR)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
CNetworkAddress::CNetworkAddress()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
CNetworkAddress::~CNetworkAddress()
|
||||
{
|
||||
}
|
||||
|
||||
CNetworkAddress::operator const SOCKADDR_IN&() const
|
||||
{
|
||||
return m_sockAddrIn;
|
||||
}
|
||||
|
||||
void CNetworkAddress::Clear()
|
||||
{
|
||||
memset(&m_sockAddrIn, 0, sizeof(m_sockAddrIn));
|
||||
m_sockAddrIn.sin_family=AF_INET;
|
||||
}
|
||||
|
||||
bool CNetworkAddress::IsIP(const char* c_szAddr)
|
||||
{
|
||||
if (c_szAddr[0]<'0' || c_szAddr[0]>'9')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetworkAddress::Set(const char* c_szAddr, int port)
|
||||
{
|
||||
if (IsIP(c_szAddr))
|
||||
{
|
||||
SetIP(c_szAddr);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!SetDNS(c_szAddr))
|
||||
return false;
|
||||
}
|
||||
|
||||
SetPort(port);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetworkAddress::SetLocalIP()
|
||||
{
|
||||
SetIP(INADDR_ANY);
|
||||
}
|
||||
|
||||
void CNetworkAddress::SetIP(DWORD ip)
|
||||
{
|
||||
m_sockAddrIn.sin_addr.s_addr=htonl(ip);
|
||||
}
|
||||
|
||||
void CNetworkAddress::SetIP(const char* c_szIP)
|
||||
{
|
||||
m_sockAddrIn.sin_addr.s_addr=inet_addr(c_szIP);
|
||||
}
|
||||
|
||||
bool CNetworkAddress::SetDNS(const char* c_szDNS)
|
||||
{
|
||||
HOSTENT* pHostent=gethostbyname(c_szDNS);
|
||||
if (!pHostent) return false;
|
||||
memcpy(&m_sockAddrIn.sin_addr, pHostent->h_addr, sizeof(m_sockAddrIn.sin_addr));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetworkAddress::SetPort(int port)
|
||||
{
|
||||
m_sockAddrIn.sin_port = htons(port);
|
||||
}
|
||||
|
||||
int CNetworkAddress::GetSize()
|
||||
{
|
||||
return sizeof(m_sockAddrIn);
|
||||
}
|
||||
|
||||
DWORD CNetworkAddress::GetIP()
|
||||
{
|
||||
return ntohl(m_sockAddrIn.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
void CNetworkAddress::GetIP(char* szIP, int len)
|
||||
{
|
||||
BYTE IPs[4];
|
||||
*((DWORD*)IPs)=m_sockAddrIn.sin_addr.s_addr;
|
||||
|
||||
_snprintf(szIP, len, "%d.%d.%d.%d", IPs[0], IPs[1], IPs[2], IPs[3]);
|
||||
}
|
||||
|
||||
int CNetworkAddress::GetPort()
|
||||
{
|
||||
return ntohs(m_sockAddrIn.sin_port);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "StdAfx.h"
|
||||
#include "NetDevice.h"
|
||||
|
||||
CNetworkDevice::CNetworkDevice()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CNetworkDevice::~CNetworkDevice()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CNetworkDevice::Initialize()
|
||||
{
|
||||
m_isWSA=false;
|
||||
}
|
||||
|
||||
void CNetworkDevice::Destroy()
|
||||
{
|
||||
if (m_isWSA)
|
||||
{
|
||||
WSACleanup();
|
||||
m_isWSA=false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CNetworkDevice::Create()
|
||||
{
|
||||
Destroy();
|
||||
|
||||
Initialize();
|
||||
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(1, 1), &wsaData)!=0)
|
||||
return false;
|
||||
|
||||
m_isWSA=true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "StdAfx.h"
|
||||
#include "NetPacketHeaderMap.h"
|
||||
|
||||
void CNetworkPacketHeaderMap::Set(int header, TPacketType rPacketType)
|
||||
{
|
||||
m_headerMap[header] = rPacketType;
|
||||
}
|
||||
bool CNetworkPacketHeaderMap::Get(int header, TPacketType * pPacketType)
|
||||
{
|
||||
std::map<int, TPacketType>::iterator f=m_headerMap.find(header);
|
||||
|
||||
if (m_headerMap.end()==f)
|
||||
return false;
|
||||
|
||||
*pPacketType = f->second;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CNetworkPacketHeaderMap::CNetworkPacketHeaderMap()
|
||||
{
|
||||
}
|
||||
|
||||
CNetworkPacketHeaderMap::~CNetworkPacketHeaderMap()
|
||||
{
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,75 @@
|
||||
#pragma once
|
||||
// Shim for <winsock.h> on non-Windows targets: the BSD socket types under their Winsock names.
|
||||
// Shim for <winsock.h> on non-Windows targets: the BSD socket API under its Winsock names, with the
|
||||
// Winsock semantics 40250 relies on where the two differ (see the notes on each function).
|
||||
#include "windows.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef int SOCKET;
|
||||
typedef struct sockaddr SOCKADDR;
|
||||
typedef struct sockaddr* PSOCKADDR;
|
||||
typedef struct sockaddr_in SOCKADDR_IN;
|
||||
typedef struct in_addr IN_ADDR;
|
||||
typedef struct timeval TIMEVAL;
|
||||
typedef struct hostent HOSTENT;
|
||||
#define INVALID_SOCKET (-1)
|
||||
#define SOCKET_ERROR (-1)
|
||||
#define WSAEWOULDBLOCK EWOULDBLOCK
|
||||
|
||||
typedef struct WSAData
|
||||
{
|
||||
WORD wVersion;
|
||||
WORD wHighVersion;
|
||||
} WSADATA;
|
||||
|
||||
// There is no Winsock DLL to start. A send on a socket the peer has closed raises SIGPIPE on POSIX
|
||||
// where Winsock returns an error, so the process ignores it from here on.
|
||||
inline int WSAStartup(WORD version, WSADATA* data)
|
||||
{
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
if (data)
|
||||
data->wVersion = data->wHighVersion = version;
|
||||
return 0;
|
||||
}
|
||||
inline int WSACleanup() { return 0; }
|
||||
|
||||
// A non-blocking connect() answers WSAEWOULDBLOCK on Winsock and EINPROGRESS on POSIX.
|
||||
inline int WSAGetLastError() { return errno == EINPROGRESS ? WSAEWOULDBLOCK : errno; }
|
||||
|
||||
inline int closesocket(SOCKET s) { return close(s); }
|
||||
|
||||
inline int ioctlsocket(SOCKET s, long cmd, DWORD* argp)
|
||||
{
|
||||
int value = static_cast<int>(*argp);
|
||||
return ioctl(s, static_cast<unsigned long>(cmd), &value);
|
||||
}
|
||||
|
||||
// Winsock ignores nfds, and a socket whose connect failed is reported only in exceptfds; POSIX needs
|
||||
// nfds and marks such a socket writable. Callers pass 0 and treat "writable" as "connected"
|
||||
// (CNetworkStream::Process), so writable sockets without a peer are dropped from the write set.
|
||||
inline int mt_winsock_select(fd_set* readfds, fd_set* writefds, fd_set* exceptfds, TIMEVAL* timeout)
|
||||
{
|
||||
int ret = ::select(FD_SETSIZE, readfds, writefds, exceptfds, timeout);
|
||||
if (ret <= 0 || !writefds)
|
||||
return ret;
|
||||
for (int fd = 0; fd < FD_SETSIZE; ++fd)
|
||||
{
|
||||
if (!FD_ISSET(fd, writefds))
|
||||
continue;
|
||||
sockaddr_in peer;
|
||||
socklen_t len = sizeof(peer);
|
||||
if (getpeername(fd, reinterpret_cast<sockaddr*>(&peer), &len) != 0)
|
||||
FD_CLR(fd, writefds);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#define select(nfds, readfds, writefds, exceptfds, timeout) mt_winsock_select(readfds, writefds, exceptfds, timeout)
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// 批次 2V1-a: the 40250 EterLib network layer (CNetworkStream over the winsock shim) against a
|
||||
// loopback peer: non-blocking connect, buffered send/recv, the connect-failure timeout, and the
|
||||
// Crypto++ key agreement (_IMPROVED_PACKET_ENCRYPTION_) with a second Cipher playing the server.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/NetDevice.h"
|
||||
#include "EterLib/NetStream.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
static int g_failures = 0;
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \
|
||||
++g_failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
class TestStream : public CNetworkStream
|
||||
{
|
||||
public:
|
||||
int successes = 0, failures = 0, remote_disconnects = 0;
|
||||
|
||||
using CNetworkStream::Prepare;
|
||||
using CNetworkStream::Activate;
|
||||
using CNetworkStream::ActivateCipher;
|
||||
|
||||
protected:
|
||||
void OnConnectSuccess() override { ++successes; }
|
||||
void OnConnectFailure() override { ++failures; }
|
||||
void OnRemoteDisconnect() override { ++remote_disconnects; }
|
||||
};
|
||||
|
||||
static int listen_loopback(int* port)
|
||||
{
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
sockaddr_in addr = {};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
|
||||
socklen_t len = sizeof(addr);
|
||||
getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len);
|
||||
listen(fd, 1);
|
||||
*port = ntohs(addr.sin_port);
|
||||
return fd;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
static bool pump(TestStream& stream, F done, int ms = 2000)
|
||||
{
|
||||
for (int i = 0; i < ms; ++i)
|
||||
{
|
||||
stream.Process();
|
||||
if (done())
|
||||
return true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool read_exact(int fd, void* buf, size_t len)
|
||||
{
|
||||
char* p = static_cast<char*>(buf);
|
||||
while (len)
|
||||
{
|
||||
ssize_t n = recv(fd, p, len, 0);
|
||||
if (n <= 0)
|
||||
return false;
|
||||
p += n;
|
||||
len -= static_cast<size_t>(n);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void connect_and_exchange()
|
||||
{
|
||||
int port = 0;
|
||||
int listener = listen_loopback(&port);
|
||||
|
||||
TestStream stream;
|
||||
stream.SetRecvBufferSize(4096);
|
||||
stream.SetSendBufferSize(4096);
|
||||
CHECK(stream.Connect("127.0.0.1", port));
|
||||
CHECK(pump(stream, [&] { return stream.IsOnline(); }));
|
||||
CHECK(stream.successes == 1 && stream.failures == 0);
|
||||
int peer = accept(listener, nullptr, nullptr);
|
||||
CHECK(peer >= 0);
|
||||
|
||||
// Plain bytes both ways; Send only queues, Process flushes.
|
||||
CHECK(stream.Send(5, "hello"));
|
||||
pump(stream, [] { return true; }, 1);
|
||||
char got[16] = {};
|
||||
CHECK(read_exact(peer, got, 5) && std::memcmp(got, "hello", 5) == 0);
|
||||
send(peer, "world!", 6, 0);
|
||||
CHECK(pump(stream, [&] { return stream.GetRecvBufferSize() >= 6; }));
|
||||
char peek[6] = {};
|
||||
CHECK(stream.Peek(6, peek) && std::memcmp(peek, "world!", 6) == 0);
|
||||
CHECK(stream.Recv(6, got) && stream.GetRecvBufferSize() == 0);
|
||||
CHECK(!stream.Recv(1, got));
|
||||
|
||||
// Key agreement: each side prepares, swaps its public value and activates with the opposite
|
||||
// polarity (the 40250 game server calls Activate(false, ...)).
|
||||
Cipher server;
|
||||
std::vector<unsigned char> client_pub(1024), server_pub(1024);
|
||||
size_t client_len = client_pub.size(), server_len = server_pub.size();
|
||||
size_t client_agreed = stream.Prepare(client_pub.data(), &client_len);
|
||||
size_t server_agreed = server.Prepare(server_pub.data(), &server_len);
|
||||
CHECK(client_agreed > 0 && client_agreed == server_agreed);
|
||||
CHECK(stream.Activate(client_agreed, server_pub.data(), server_len));
|
||||
CHECK(server.Activate(false, server_agreed, client_pub.data(), client_len));
|
||||
stream.ActivateCipher();
|
||||
server.set_activated(true);
|
||||
CHECK(stream.IsSecurityMode());
|
||||
|
||||
// Client → server: ciphertext on the wire, the server's decoder restores it.
|
||||
const char secret[] = "secret-login-packet";
|
||||
CHECK(stream.Send(sizeof(secret), secret));
|
||||
pump(stream, [] { return true; }, 1);
|
||||
char wire[sizeof(secret)] = {};
|
||||
CHECK(read_exact(peer, wire, sizeof(wire)));
|
||||
CHECK(std::memcmp(wire, secret, sizeof(secret)) != 0);
|
||||
server.Decrypt(wire, sizeof(wire));
|
||||
CHECK(std::memcmp(wire, secret, sizeof(secret)) == 0);
|
||||
|
||||
// Server → client.
|
||||
char reply[] = "phase-select";
|
||||
server.Encrypt(reply, sizeof(reply));
|
||||
send(peer, reply, sizeof(reply), 0);
|
||||
CHECK(pump(stream, [&] { return stream.GetRecvBufferSize() >= static_cast<int>(sizeof(reply)); }));
|
||||
CHECK(stream.Recv(sizeof(reply), got) && std::strcmp(got, "phase-select") == 0);
|
||||
|
||||
// The peer hangs up: recv answers 0, Process reports it once and clears the stream.
|
||||
close(peer);
|
||||
CHECK(pump(stream, [&] { return stream.remote_disconnects > 0; }));
|
||||
CHECK(stream.remote_disconnects == 1 && !stream.IsOnline() && !stream.IsSecurityMode());
|
||||
close(listener);
|
||||
}
|
||||
|
||||
static void connect_refused()
|
||||
{
|
||||
// A port nobody listens on: Winsock reports the failed connect only in exceptfds, which
|
||||
// CNetworkStream::Process does not pass, so the stream waits out the limit and then fails.
|
||||
int port = 0;
|
||||
int listener = listen_loopback(&port);
|
||||
close(listener);
|
||||
|
||||
TestStream stream;
|
||||
CNetworkAddress addr;
|
||||
addr.Set("127.0.0.1", port);
|
||||
CHECK(stream.Connect(addr, 1));
|
||||
CHECK(pump(stream, [&] { return stream.failures > 0; }, 3000));
|
||||
CHECK(stream.successes == 0 && stream.failures == 1 && !stream.IsOnline());
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
CNetworkDevice device;
|
||||
CHECK(device.Create());
|
||||
connect_and_exchange();
|
||||
connect_refused();
|
||||
|
||||
if (g_failures)
|
||||
{
|
||||
std::fprintf(stderr, "%d check(s) failed\n", g_failures);
|
||||
return 1;
|
||||
}
|
||||
std::printf("port_net_test: ok\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user