I have found some old code in perl that will create a packet to be sent off to a program:
The relevant part is:
my $discovery_socket = IO::Socket::INET->new(
Proto => 'udp',
LocalAddr => '0.0.0.0',
LocalPort => 3040,
) or die "Can't bind udp/3040: $!";
my $discovery_io = IO::Async::Socket->new(
handle => $discovery_socket,
on_recv => sub {
my ($self, $dgram, $addr) = @_;
my ($err, $hostname, $portname) = Socket::getnameinfo($addr);
my (undef, undef, $seq) = unpack "ccna6xx", $dgram;
print "Received identify with seq $seq from $hostname/$portname\n";
my $header = pack "ccna6xx",
0x5a, # magic
1, # identify reply
$seq,
"\x00\x24\x1d\x8b\x0e\x50";
my $identify = pack "nccccccC4C4C4a4a4a4a32",
1, 1, 1, 1, # uptime
1, # mode
0, # alert
4, # ip type
192, 168, 1, 3, # ip addr
255, 255, 255, 0, # netmask
192, 168, 1, 1, # gateway
"\x01\x02\x03\x04", # app ver
"\x01\x02\x03\x04", # boot ver
"\x01\x02\x03\x04", # app ver
"PL ETH/USB"; # name
send $discovery_socket, $header . $identify, 0, $addr;
},
on_recv_error => sub {
my ($self, $errno) = @_;
die "recv error $errno on discovery";
}
);
I've attempted to recreate this with the following function in c++, (plus other surrounding code that probably isn't relevant:
void handle_receive(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if (!error || error == boost::asio::error::message_size) {
std::string hostname = remote_endpoint_.address().to_string();
unsigned short port = remote_endpoint_.port();
std::cout << "Received identify from " << hostname << "/" << port << std::endl;
uint16_t seq;
std::memcpy(&seq, recv_buffer_.data() + 2, sizeof(seq));
// Construct header
std::array<char, 64> header;
header[0] = 0x5A; // magic
header[1] = 1; // identify reply
std::memcpy(header.data() + 2, &seq, sizeof(seq));
std::memcpy(header.data() + 4, "\x00\x24\x1d\x8b\x0e\x50", 6); // eth_addr
// Construct identify reply
std::array<char, 64> identify_reply;
std::memset(identify_reply.data(), 0, 4); // uptime
identify_reply[4] = 1; // mode
identify_reply[5] = 0; // alert
identify_reply[6] = 4; // ip type
std::memcpy(identify_reply.data() + 7, "\x0a\x0a\x0a\x0a", 4); // ip addr: 10.10.10.10
std::memcpy(identify_reply.data() + 11, "\xff\xff\xff\x00", 4); // netmask
std::memcpy(identify_reply.data() + 15, "\x0a\x0a\x0a\x01", 4); // gateway
std::memcpy(identify_reply.data() + 19, "\x01\x02\x03\x04", 4); // app ver
std::memcpy(identify_reply.data() + 23, "\x01\x02\x03\x04", 4); // boot ver
std::memcpy(identify_reply.data() + 27, "\x01\x02\x03\x04", 4); // hw ver
std::memcpy(identify_reply.data() + 31, "PL ETH/USB", 10); // name
// Convert header and identify reply to vector of chars
std::vector<char> send_buffer(header.begin(), header.end());
send_buffer.insert(send_buffer.end(), identify_reply.begin(), identify_reply.end());
// Convert packet to vector of strings
std::vector<std::string> packetStrings = convertPacketToStrings(header, identify_reply,false);
// Print each string to console
for (const auto& str : packetStrings) {
std::cout << str << std::endl;
}
// Send packet
socket_.send_to(boost::asio::buffer(send_buffer), remote_endpoint_);
// Start receiving again
start_receive();
}
}
The program that send a udp packet to this server code will get a discovery packet as a response, which the program does infact see as follows:
But the IP is not correct, it seems to be some randomly generated sequence of numbers that show up at byte position 21, in the following hex -> decimal sequence, which I highlighted in bold.
90 1 81 79 0 36 29 139 14 80 61 4 1 0 0 0 92 251 245 181 1 128 62 228 104 46 166 107 1 0 0 0 72 46 166 107 1 0 0 0 0 0 0 0 1 0 0 0 200 95 59 4 1 0 0 0 16 192 76 1 0 96 0 0
0 0 0 0 1 0 4 10 10 10 10 255 255 255 0 10 10 10 1 1 2 3 4 1 2 3 4 1 2 3 4 80 76 32 69 84 72 47 85 83 66 67 224 74 1 0 0 0 72 69 224 74 1 0 0 0 208 0 0 0 0 0 0 0
I'm sure the structure or byte order is wrong somehow, but I don't see it.
Any ideas?
Looking at the perl docs, n
indicates packing a 16bit unsigned big-endian integer:
n An unsigned short (16-bit) in "network" (big-endian) order.
That implies that pack "nccccccC4C4C4a4a4a4a32"
puts the parts at these offsets:
my $identify = pack "nccccccC4C4C4a4a4a4a32",
1, 1, 1, 1, # uptime n c c c, offset 0
1, # mode c, offset 5
0, # alert c, offset 6
4, # ip type c, offset 7
192, 168, 1, 3, # ip addr, offset 8
255, 255, 255, 0, # netmask, offset 12
192, 168, 1, 1, # gateway, offset 16
"\x01\x02\x03\x04", # app ver, offset 20
"\x01\x02\x03\x04", # boot ver, offset 24
"\x01\x02\x03\x04", # app ver, offset 28
"PL ETH/USB"; # name, offset 32
I have confirmed this with the simplified perl script:
#!/usr/bin/perl
use strict;
use warnings;
# my (undef, undef, $seq) = unpack "ccna6xx", $dgram;
my $seq = 1;
print "Received identify with seq $seq\n";
my $header = pack "ccna6xx",
0x5a, # magic
1, # identify reply
$seq,
"\x00\x24\x1d\x8b\x0e\x50"; # mac addr of the device (00:24:1d:8b:0e:50)
my $identify = pack "nccccccC4C4C4a4a4a4a32",
1, 1, 1, 1, # uptime
1, # mode
0, # alert
4, # ip type
192, 168, 1, 3, # ip addr
255, 255, 255, 0, # netmask
192, 168, 1, 1, # gateway
"\x01\x02\x03\x04", # app ver
"\x01\x02\x03\x04", # boot ver
"\x01\x02\x03\x04", # app ver
"PL ETH/USB"; # name
# print both header and identify as hex arrays to stdout
print join " ", map { sprintf "%02x", $_ } unpack "C*", $header;
print "\n";
print join " ", map { sprintf "%02x", $_ } unpack "C*", $identify;
print "\n";
Which outputs Live On Coliru:
# header
5a 01 00 01 00 24 1d 8b 0e 50 00 00
# identify reply
00 01 01 01 01 01 00 04 c0 a8 01 03 ff ff ff 00 c0 a8 01 01 01 02 03 04 01 02 03 04 01 02 03 04 50 4c 20 45 54 48 2f 55 53 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
So that confirms my counts (see netmask start at offset #12).
Regardless, this doesn't do the same thing:
std::vector<char> send_buffer(header.begin(), header.end());
That adds all header bytes, which is 64 bytes. The perl version only has 12 header bytes. I would probably mimic it like so:
// Construct header
std::vector<uint8_t> header{
0x5A, // magic
1, // identify reply
seq[0], seq[1], // seq, still big endian
0x00, 0x24, 0x1d, 0x8b, 0x0e, 0x50, // eth_addr 00:24:1d:8b:0e:50
0x00, 0x00 // trailing xx
};
assert(header.size() == 12);
Now the identify reply could be something like:
// Construct identify reply
std::array<uint8_t, 64> identify_reply{
0, 1, 0, 1, 0, 0, 0, 4, // uptime, mode, alert, ip type
192, 168, 1, 3, // ip addr
255, 255, 255, 0, // netmask
192, 168, 1, 1, // gateway
1, 2, 3, 4, // app ver
1, 2, 3, 4, // boot ver
1, 2, 3, 4, // hw ver
'P', 'L', ' ', 'E', 'T', 'H', '/', 'U', 'S', 'B' // name
// 22 (64-42) trailing NUL bytes for free
};
To deal with the network ordering, you could do e.g.:
uint16_t uptime = ::htons(1);
std::memcpy(identify_reply.data() + 0, &uptime, 2);
Or, vice versa:
uint8_t rawseq[2]; // big endian
std::memcpy(&rawseq, recv_buffer_.data() + 2, sizeof(rawseq));
{
uint16_t seq;
std::memcpy(&seq, recv_buffer_.data() + 2, sizeof(seq));
seq = ntohs(seq);
}
#include <array>
#include <cassert>
#include <fmt/ranges.h>
#include <netinet/in.h>
#include <vector>
void simplified_handle_receive() {
std::vector<uint8_t> recv_buffer_{0x5A, 0x01, 0x00, 0x01, 0x00, 0x24,
0x1d, 0x8b, 0x0e, 0x50, 0x00, 0x00}; // no idea here, really
uint8_t rawseq[2];
std::memcpy(&rawseq, recv_buffer_.data() + 2, sizeof(rawseq));
{
uint16_t seq;
std::memcpy(&seq, recv_buffer_.data() + 2, sizeof(seq));
seq = ntohs(seq);
}
// Construct header
std::vector<uint8_t> buffer{
0x5A, // magic
1, // identify reply
rawseq[0], rawseq[1], // seq, still big endian
0x00, 0x24, 0x1d, 0x8b, 0x0e, 0x50, // eth_addr 00:24:1d:8b:0e:50
0x00, 0x00 // trailing xx
};
assert(buffer.size() == 12);
// Construct identify reply
std::array<uint8_t, 64> identify_reply{
0, 1, 0, 1, 0, 0, 0, 4, // uptime, mode, alert, ip type
192, 168, 1, 3, // ip addr
255, 255, 255, 0, // netmask
192, 168, 1, 1, // gateway
1, 2, 3, 4, // app ver
1, 2, 3, 4, // boot ver
1, 2, 3, 4, // hw ver
'P', 'L', ' ', 'E', 'T', 'H', '/', 'U',
'S', 'B' // name
// 22 (64-42) trailing NUL bytes for free
};
fmt::print("Header: {::02x}\n", buffer);
fmt::print("Identify reply: {::02x}\n", identify_reply);
buffer.insert(buffer.end(), identify_reply.begin(), identify_reply.end());
fmt::print("Header + Identify reply: {::02x}\n", buffer);
};
int main() {
simplified_handle_receive();
}
Printing
Header: [5a, 01, 00, 01, 00, 24, 1d, 8b, 0e, 50, 00, 00]
Identify reply: [00, 01, 00, 01, 00, 00, 00, 04, c0, a8, 01, 03, ff, ff, ff, 00, c0, a8, 01, 01, 01, 02, 03, 04, 01, 02, 03, 04, 01, 02, 03, 04, 50, 4c, 20, 45, 54, 48, 2f, 55, 53, 42, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]
Header + Identify reply: [5a, 01, 00, 01, 00, 24, 1d, 8b, 0e, 50, 00, 00, 00, 01, 00, 01, 00, 00, 00, 04, c0, a8, 01, 03, ff, ff, ff, 00, c0, a8, 01, 01, 01, 02, 03, 04, 01, 02, 03, 04, 01, 02, 03, 04, 50, 4c, 20, 45, 54, 48, 2f, 55, 53, 42, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]