1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
// Local regression test for the pool's rendezvous accept path.
//
// Reproduces the exact failure that took remote miners off the pool: since
// upstream #30, RvServiceRegister returns as soon as the relay lists the
// service and no longer consumes the relay's 0x01 pairing byte. A service that
// goes straight from RvServiceRegister to BtfServiceWrap therefore reads that
// 0x01 as the first byte of the client's 32-byte ephemeral key, shifting the
// whole stream by one. X25519 has no confirmation step, so the handshake
// "succeeds" against a garbage shared secret and the corruption only shows up
// as a tunnel that never carries a valid byte.
//
// Build: g++ -std=gnu++14 -O2 -pthread test_rvaccept.cpp btftunnel.cpp btfrv.cpp \
// btfchan.cpp -lsodium -o test_rvaccept
//
// Build: g++ -std=gnu++14 -O2 -pthread test_rvaccept.cpp btftunnel.cpp \
// btfrv.cpp btfchan.cpp -lsodium -o test_rvaccept
//
// Run against a bitflashd relay already listening on 127.0.0.1:<port>.
// test_rvaccept broken <port> -- the pre-fix pool code path, expected to FAIL
// test_rvaccept fixed <port> -- with RvServiceWaitPaired, expected to PASS
#include "btftunnel.h"
#include "btfrv.h"
#include "btfchan.h"
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <thread>
#include <chrono>
using namespace btf;
static const char* HOST = "127.0.0.1";
static unsigned short PORT = 18446;
static const char* MSG = "mining.subscribe stand-in payload";
static void SetTimeout(int fd, int secs)
{
struct timeval tv;
tv.tv_sec = secs;
tv.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
}
static bool RecvN(int fd, void* buf, int n)
{
char* p = (char*)buf;
int off = 0;
while (off < n) {
int r = recv(fd, p + off, n - off, 0);
if (r <= 0) return false;
off += r;
}
return true;
}
static bool SendN(int fd, const void* buf, int n)
{
const char* p = (const char*)buf;
int off = 0;
while (off < n) {
int r = send(fd, p + off, n - off, 0);
if (r <= 0) return false;
off += r;
}
return true;
}
static bool g_serviceEcho = false;
static std::string g_serviceSaw;
// The pool's AcceptOneFn, in both shapes.
static void ServiceThread(bool fWaitPaired,
std::vector<unsigned char> id,
std::vector<unsigned char> enc_sk,
int len)
{
RvSocket rv = RvServiceRegister(HOST, PORT, id.data());
if (rv == RV_INVALID) { printf(" service: RvServiceRegister failed\n"); return; }
if (fWaitPaired) {
if (!RvServiceWaitPaired(rv)) {
printf(" service: RvServiceWaitPaired failed\n");
RvClose(rv);
return;
}
printf(" service: paired (0x01 consumed by RvServiceWaitPaired)\n");
} else {
printf(" service: skipped RvServiceWaitPaired (pre-fix behaviour)\n");
}
btf_socket_t app = BtfServiceWrap(rv, enc_sk.data());
if (app == INVALID_SOCKET) { printf(" service: BtfServiceWrap failed\n"); return; }
printf(" service: BtfServiceWrap returned a tunnel\n");
SetTimeout(app, 5);
std::vector<char> buf(len);
if (!RecvN(app, buf.data(), len)) {
printf(" service: never received the client's plaintext\n");
close(app);
return;
}
g_serviceSaw.assign(buf.data(), len);
g_serviceEcho = SendN(app, buf.data(), len);
close(app);
}
int main(int argc, char* argv[])
{
bool fWaitPaired = (argc >= 2 && strcmp(argv[1], "fixed") == 0);
if (argc >= 3) PORT = (unsigned short)atoi(argv[2]);
if (!TunnelInit()) { printf("TunnelInit failed\n"); return 2; }
unsigned char enc_pk[32], enc_sk[32];
ChanKeypair(enc_pk, enc_sk);
std::vector<unsigned char> id(32);
for (int i = 0; i < 32; i++) id[i] = (unsigned char)(i * 7 + 13);
const std::string msg(MSG);
const int len = (int)msg.size();
printf(" mode: %s relay: %s:%u\n", fWaitPaired ? "fixed" : "broken", HOST, PORT);
std::thread svc(ServiceThread, fWaitPaired, id,
std::vector<unsigned char>(enc_sk, enc_sk + 32), len);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
int nFail = 0;
btf_socket_t app = BtfClientTunnel(HOST, PORT, id.data(), enc_pk);
if (app == INVALID_SOCKET) {
printf(" client: BtfClientTunnel failed\n");
nFail++;
} else {
printf(" client: tunnel established\n");
SetTimeout(app, 5);
if (!SendN(app, msg.data(), len)) { printf(" client: send failed\n"); nFail++; }
std::vector<char> echo(len);
if (!RecvN(app, echo.data(), len)) {
printf(" client: no echo came back\n");
nFail++;
} else if (std::string(echo.data(), len) != msg) {
printf(" client: echo did not match what was sent\n");
nFail++;
} else {
printf(" client: round-tripped the payload intact\n");
}
close(app);
}
svc.join();
if (g_serviceSaw != msg) {
printf(" service did not see the correct plaintext (saw %d bytes)\n",
(int)g_serviceSaw.size());
nFail++;
}
if (!g_serviceEcho) { printf(" service could not echo\n"); nFail++; }
bool fPassed = (nFail == 0);
printf(" RESULT: %s\n", fPassed ? "PASS -- tunnel carries plaintext end to end"
: "FAIL -- tunnel never carried a valid byte");
// A broken run is the expected outcome pre-fix, so exit 0 when the
// observed behaviour matches the expectation for the mode under test.
bool fAsExpected = (fWaitPaired ? fPassed : !fPassed);
printf(" EXPECTED %s -> %s\n",
fWaitPaired ? "PASS" : "FAIL",
fAsExpected ? "as expected" : "UNEXPECTED");
return fAsExpected ? 0 : 1;
}
|