Problems with ICMP socket socket2 crate - sockets

I've written an application to send and receive ICMP packets (a ping redo, so to speak).
I've tested this on different computers and found that the code only runs on my MacOS. Other linux machines (I tested on many different flavors of Linux) gave wrong results, and I don't even know where to debug anymore.
Expected output, this is what comes out of my MacOS
[2022-06-17T18:37:47Z INFO layer4_ip] Received 84 bytes from 142.250.69.206:0
[2022-06-17T18:37:47Z INFO layer4_ip] Received Ipv4Packet { version : 4, header_length : 5, dscp : 0, ecn : 0, total_length : 16384, identification : 0, flags : 0, fragment_offset : 0, ttl : 119, next_level_protocol : IpNextHeaderProtocol(1), checksum : 44214, source : 142.250.69.206, destination : 192.168.1.130, options : [], }
[2022-06-17T18:37:48Z INFO layer4_ip] Received 84 bytes from 142.250.69.206:0
[2022-06-17T18:37:48Z INFO layer4_ip] Received Ipv4Packet { version : 4, header_length : 5, dscp : 0, ecn : 0, total_length : 16384, identification : 0, flags : 0, fragment_offset : 0, ttl : 119, next_level_protocol : IpNextHeaderProtocol(1), checksum : 44214, source : 142.250.69.206, destination : 192.168.1.130, options : [], }
Linux output (wrong output):
[2022-06-17T18:32:54Z INFO ping_playground] Received 64 bytes from 142.250.69.206:0
[2022-06-17T18:32:54Z INFO ping_playground] Received Ipv4Packet { version : 0, header_length : 0, dscp : 0, ecn : 0, total_length : 65454, identification : 80, flags : 0, fragment_offset : 1, ttl : 0, next_level_protocol : IpNextHeaderProtocol(0), checksum : 0, source : 0.0.0.0, destination : 0.0.0.0, options : [], }
[2022-06-17T18:32:55Z INFO ping_playground] Received 64 bytes from 142.250.69.206:0
[2022-06-17T18:32:55Z INFO ping_playground] Received Ipv4Packet { version : 0, header_length : 0, dscp : 0, ecn : 0, total_length : 65454, identification : 80, flags : 0, fragment_offset : 1, ttl : 0, next_level_protocol : IpNextHeaderProtocol(0), checksum : 0, source : 0.0.0.0, destination : 0.0.0.0, options : [], }
Not only the number of bytes read is different, but the parsing is wrong.
Intercepting in wireshark shows that the packets that are received back are indeed the same on my MacOS and Linux machines.
Here's the minimal version that presents the problem:
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
os::unix::prelude::{AsRawFd, FromRawFd},
sync::Arc,
time::Duration,
};
use env_logger::Env;
use log::info;
use pnet_packet::{
icmp::{self},
Packet,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
// SOURCE IP ADDRESS
// let localhost = Ipv4Addr::LOCALHOST;
let localhost = Ipv4Addr::UNSPECIFIED;
let socket_ip_address = SocketAddr::new(IpAddr::V4(localhost), 80);
let socket2_ip_address = socket_ip_address.into();
// CREATE ICMP SOCKET
let socket2_ipv4_socket = socket2::Socket::new(
socket2::Domain::IPV4,
socket2::Type::DGRAM,
Some(socket2::Protocol::ICMPV4),
)
.unwrap();
// BIND TO LOCAL ADDRESS
socket2_ipv4_socket
.bind(&socket2_ip_address)
.expect(&format!(
"Failed binding to Ipv4 address {:?}",
&socket_ip_address
));
// CREATE STD SOCKET FROM SOCKET2 SOCKET
let raw_ipv4_socket = socket2_ipv4_socket.as_raw_fd();
let std_ipv4_socket = unsafe { std::net::UdpSocket::from_raw_fd(raw_ipv4_socket) };
std_ipv4_socket.set_read_timeout(Some(Duration::from_millis(100)))?;
let socket_arc = Arc::new(std_ipv4_socket);
let dest = "142.250.69.206:0";
let mut buffer = [0; 1024];
let socket_clone = Arc::clone(&socket_arc);
std::thread::spawn(move || {
let packet_slice = &mut [0; 56];
let mut buf = vec![0; 8 + 56]; // 8 bytes of header, then payload
let mut packet = icmp::echo_request::MutableEchoRequestPacket::new(&mut buf[..]).unwrap();
packet.set_icmp_type(icmp::IcmpTypes::EchoRequest);
packet.set_identifier(1);
packet.set_sequence_number(1);
packet.set_payload(packet_slice);
// Calculate and set the checksum
let icmp_packet = icmp::IcmpPacket::new(packet.packet()).unwrap();
let checksum = icmp::checksum(&icmp_packet);
packet.set_checksum(checksum);
loop {
socket_clone.send_to(&mut packet.packet(), dest).unwrap();
std::thread::sleep(Duration::from_millis(1000));
}
});
loop {
if let Ok((bytes_read, from)) = socket_arc.recv_from(&mut buffer) {
info!("Received {} bytes from {:?}", bytes_read, from);
let ipv4_packet = pnet_packet::ipv4::Ipv4Packet::new(&buffer).unwrap();
let _icmp_packet = pnet_packet::icmp::IcmpPacket::new(ipv4_packet.payload()).unwrap();
let _udp_packet = pnet_packet::udp::UdpPacket::new(&ipv4_packet.payload()).unwrap();
info!("Received {:?}", ipv4_packet);
}
}
}
Here's the dependencies part of Cargo.toml:
[dependencies]
pnet_packet = "0.29"
log = "0.4"
env_logger = "0.9"
socket2 = "0.4"
First, I would like someone to confirm this behavior.
Second, I would like help in figuring out what's wrong.
Thank you

Dummy me.
To receive the IP header I had to use a RAW socket and not the DGRAM.
Changing the socket creation to:
let socket2_ipv4_socket = socket2::Socket::new(
socket2::Domain::IPV4,
socket2::Type::RAW,
Some(socket2::Protocol::ICMPV4),
)
.unwrap();
That comes with the caveat that you have to give the right capabilities to your binary. Something like this, for example:
cargo build && sudo sudo setcap cap_net_raw+ep target/debug/ping_playground && target/debug/ping_playground
Closing the question.

Related

how to slove modbus_read_registers read error: Operation not permitted?

I used ubuntu16 socat to generate a pair of virtual serial ports and wrote code to try Modbus RTU communication, but the host got an error reading slave map holdover register address: read error: Operation not permitted
master.c
ret = modbus_read_registers(mb, 2000, 3, table);
if(ret == 3)
printf("read success : 0x%02x 0x%02x 0x%02x \n", table[0], table[1], table[2]);
else
{
printf("read error: %s\n", modbus_strerror(errno));
break;
}
slave.c
mb_mapping = modbus_mapping_new_start_address(0, 0, 0, 0, 2000, 3, 0, 0);
if(mb_mapping == NULL)
{
modbus_free(mb);
printf("new mapping failed: %s\n", modbus_strerror(errno));
return 0;
}
//保持寄存器数据
mb_mapping->tab_registers[0] = 0x1001;
mb_mapping->tab_registers[1] = 0x1002;
mb_mapping->tab_registers[2] = 0x1003;
socat:
error:
I consulted a lot of materials and tried to solve the problem, but in the end there was no result

Rust OSError 22, Invalid argument when writing valid data to socket

I'm struggling to understand why I'm getting this error from a part of my program which sends ICMP echo requests on the network. The starnge thing about this is that I can get it to work by letting the socket handle the IP header, but when I set the IP_HDRINCL option and give it a valid IP header, it returns EINVAL error:
initialize
using interface en0 with ip 192.168.1.126 and mac a4:83:e7:43:40:81.
Input start ip/scan range: 192.168.1.1
[45, 0, 0, 1c, 20, 1, 40, 0, 40, 1, 97, 10, c0, a8, 1, 7e, c0, a8, 1, 1, 8, 0, 22, 2a, 97, 3e, 3e, 97]
[69, 0, 0, 28, 32, 1, 64, 0, 64, 1, 151, 16, 192, 168, 1, 126, 192, 168, 1, 1, 8, 0, 34, 42, 151, 62, 62, 151]
Err(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })
To help visualise what's going on, I've put dumps of the data being sent in there when it sends it. the first dump is the data in hex format and the second is just the same but in decimal format.
Here is the code which opens the socket and creates/sends/receives packets:
( Apologies for the probably overwhelming amount of code, just though I should make sure I provide enough info:) )
let response = net_tools::interface::Interface::detect();
if response.is_err() {
panic! {response};
}
let active_interface = response.unwrap();
println!(
"using interface {} with ip {} and mac {}.",
active_interface.get_name(),
active_interface.get_ip_as_struct(),
active_interface.get_mac()
);
let scan_targets = net_tools::TaregtAddresses::retrieve_from_user();
let sock = net_tools::socket_tools::Socket::new_v4().expect("Failed to open socket");
let mut i = 0;
for ipv4addr in scan_targets {
if i == 1 {
break;
}
let mut d = packet_crafter::Packet::new(
active_interface.clone(),
vec![Protocols::IP, Protocols::ICMP],
// vec![Protocols::ICMP],
);
d.ip_header.set_next_protocol(Protocols::ICMP);
d.ip_header.set_dst_ip(Some(ipv4addr.octets()));
// d.ip_header.set_tos(0b11000100);
d.finalize_headers(); // calculates checksums etc
let data = d.build();
println!("{:x?}", data);
println!("{:?}", data);
let response = sock.sendto(data.as_slice(), SocketAddrV4::new(ipv4addr, 21));
println!("{:?}", response);
i += 1;
}
println!("All data sent, receiving response:");
let mut a = [0u8; 56];
let received_data = sock.recv(&mut a);
println!("{:?}", received_data);
let v = a.to_vec();
println!("{:?}", v);
This is how I'm opening a socket, and sending data on it, notice that I call setsockopt and set IP_HDRINCL to 1 when opening it:
pub fn new_v4() -> crate::io::Result<Self> {
unsafe {
let fam = libc::AF_INET; // Set domain family to ipv4
if cfg!(target_os = "linux") {
match cvt(libc::socket(fam, libc::SOCK_RAW | SOCK_CLOEXEC, 1)) {
Ok(fd) => return Ok(Self(Fd::new(fd))),
Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {}
Err(e) => return Err(e),
}
}
let _fd = cvt(libc::socket(fam, libc::SOCK_RAW, 1))?; // 1 = setting next protocol as ICMP
let _fd = Fd::new(_fd);
_fd.set_cloexec()?;
let socket = Self(_fd);
if cfg!(target_vendor = "apple") {
let payload = &1u32 as *const u32 as *const libc::c_void;
cvt(libc::setsockopt(
*socket.as_inner(),
libc::SOL_SOCKET,
SO_NOSIGPIPE,
payload,
size_of::<libc::c_int>() as libc::socklen_t,
))?;
}
let payload = &1u32 as *const u32 as *const libc::c_void;
cvt(libc::setsockopt(
*socket.as_inner(),
libc::IPPROTO_IP,
libc::IP_HDRINCL,
payload,
size_of::<libc::c_int>() as libc::socklen_t,
))?;
Ok(socket)
}
}
pub fn sendto(&self, buf: &[u8], addr: SocketAddrV4) -> crate::io::Result<usize> {
unsafe {
let (sa, l) = sock_addr_into_raw(addr);
let n = cvt({
libc::sendto(
*self.as_inner(),
buf.as_ptr() as *const libc::c_void,
cmp::min(buf.len(), super::max_len()),
MSG_NOSIGNAL,
&sa as *const _,
l,
)
})?;
Ok(n as usize)
}
}
This is the function to convert the type of the socketAddr:
pub fn sock_addr_into_raw(s: SocketAddrV4) -> (libc::sockaddr, libc::socklen_t) {
unsafe {
let mut storage = mem::MaybeUninit::<libc::sockaddr>::uninit();
let len = mem::size_of::<SocketAddrV4>();
copy_nonoverlapping(
&s as *const _ as *const libc::sockaddr as *const _ as *const u8,
&mut storage as *mut _ as *mut u8,
len,
);
(storage.assume_init(), len as _)
}
}
So back to my point, as you can see from the first segment of code, the IP header in the packet data that I dump is a valid IP header, followed by a valid ICMP header, as far as I'm aware, I have been changing things around to ensure it's valid so correct me if its not. But anyway, I know that all the other parameters being passed into sendto are valid because when I set the IP_HDRINCL socket option to 0 and only send ICMP data then it works...
initialize
using interface en0 with ip 192.168.1.126 and mac a4:83:e7:43:40:81.
Input start ip/scan range: 192.168.1.1
[8, 0, 8a, 92, 3f, 2e, 2e, 3f]
[8, 0, 138, 146, 63, 46, 46, 63]
Ok(8)
All data sent, receiving response:
Ok(28)
[69, 0, 8, 0, 224, 108, 0, 0, 254, 1, 88, 164, 192, 168, 1, 1, 192, 168, 1, 126, 0, 0, 146, 146, 63, 46, 46, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
I've even tried things such as sending an ethernet II header in the packet when IP_HDRINCL is 1, but that still just gives the same OSError. Does anyone know why sending the IP header myself breaks it??
(btw I'm running this on Mac OS Mojave 10.14.6)

Python (Flask) MongoDB Speed Issue

I have a big speed problem on my website using Flask/MongoDB as backend. A basic request (get 1 user for example) takes about 4 sec to respond.
Here is the python code :
#users_apis.route('/profile/<string:user_id>',methods= ['GET','PUT','DELETE'])
#auth_token_required
def profile(user_id):
if request.method == "GET":
avatar = ''
if user_id == str(current_user.id):
if(current_user.birthday):
age = (date.today().year - current_user.birthday.year)
else:
age = ''
return make_response(jsonify({
"id" : str(current_user.id),
"username" : current_user.username,
"email" : current_user.email,
"first_name": current_user.first_name,
"last_name" : current_user.last_name,
"age" : age,
"birthday" : current_user.birthday,
"gender" : current_user.gender,
"city" : current_user.city,
"country" : current_user.country,
"languages" : current_user.languages,
"description" : current_user.description,
"phone_number" : current_user.phone_number,
"countries_visited" : current_user.countries_visited,
"countries_to_visit" : current_user.countries_to_visit,
"zip_code" : str(current_user.zip_code),
"address" : current_user.address,
"pictures" : current_user.pictures,
"avatar" : "",
"interests" : current_user.interests,
"messages" : current_user.messages,
"invitations" : current_user.invitations,
"events" : current_user.events
}), 200)
And my mongodb database is build like this :
The selected user is nearly empty (has no friends, no events, no pictures...).
class BaseUser(db.Document, UserMixin):
username = db.StringField(max_length=64, unique=True, required=True)
email = db.EmailField(unique=True, required=True)
password = db.StringField(max_length=255, required=True)
active = db.BooleanField(default=True)
joined_on = db.DateTimeField(default=datetime.now())
roles = db.ListField(db.ReferenceField(Role), default=[])
class User(BaseUser)
# Identity
first_name = db.StringField(max_length=255)
last_name = db.StringField(max_length=255)
birthday = db.DateTimeField()
gender = db.StringField(max_length=1,choices=GENDER,default='N')
# Coordinates
address = db.StringField(max_length=255)
zip_code = db.IntField()
city = db.StringField(max_length=64)
region = db.StringField(max_length=64)
country = db.StringField(max_length=32)
phone_number = db.StringField(max_length=18)
# Community
description = db.StringField(max_length=1000)
activities = db.StringField(max_length=1000)
languages = db.ListField(db.StringField(max_length=32))
countries_visited = db.ListField(db.StringField(max_length=32))
countries_to_visit = db.ListField(db.StringField(max_length=32))
interests = db.ListField(db.ReferenceField('Tags'))
friends = db.ListField(db.ReferenceField('User'))
friend_requests = db.ListField(db.ReferenceField('User'))
pictures = db.ListField(db.ReferenceField('Picture'))
events = db.ListField(db.ReferenceField('Event'))
messages = db.ListField(db.ReferenceField('PrivateMessage'))
invitations = db.ListField(db.ReferenceField('Invitation'))
email_validated = db.BooleanField(default=False)
validation_date = db.DateTimeField()
I have a debian serveur with 6Go Ram and 1 vcore, 2,4GHz.
When I check the log for the mongoDB I don't see request that takes more then 378ms (for a search request)
If I use TOP during a request on my server:
I see for 1 sec a 97% CPU use for Python during the request.
When I check the python server output :
I see 4 second between the Option request and the Get Request.
I finally managed to "fix" my issue.
It seems all the problem was due to the #auth_token_required.
Each request done by the front end to the back end with the "headers.append('Authentication-Token',currentUser.token);" created a huge delay.
I replaced #auth_token_required by #login_required.
I m now using cookies.
Hope it helps someone.

Errors With Sending Login Packet to Minecraft Server in Python

I have the following script, which I found online, in Python. What it does is tries to connect to a MineCraft server, first by sending a 'handshake', then sending a login request. The protocol specs can be found here: http://wiki.vg/Protocol
Anyway, the python script works fine. However, I think the second packet is encoded wrong, as when it is sent, nothing appears on the server console. The player isn't connected or anything. It just eventually times out and closes the connection due to the 'client' not logging in in time.
Basically, anyway who has experience with struct.pack() should be able to help me here. I have commented the line where I am unsure of whether I have encoded everything right. The detailed information on packing the data is shown in the link above.
Any help would be greatly appreciated, I'm pretty clueless with encoding/packing data. :(
Here's the code
import struct
import socket
import time
import urllib
import urllib2
host = str(raw_input('What is the host ip: '))
port = int(raw_input('What is the server port: '))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
usrnm = str(raw_input('What is your username: '))
psswrd = str(raw_input('What is your password: '))
logindata = {'user':usrnm, 'password':psswrd, 'version':'12'}
data = urllib.urlencode(logindata)
print('Sending data to login.minecraft.net...')
req = urllib2.Request('https://login.minecraft.net', data)
response = urllib2.urlopen(req)
returndata = response.read()
returndata = returndata.split(":")
mcsessionid = returndata[3]
del req
del returndata
print("Session ID: " + mcsessionid)
data = {'user':usrnm,'host':host,'port':port}
enc_user = data['user'].encode('utf-16BE')
packfmt = '>bih{}shiibBB'.format(len(enc_user))
packetbytes = struct.pack(packfmt, 1, 23, len(data['user']), enc_user, 0, 0, 0, 0, 0, 0)
stringfmt = u'%(user)s;%(host)s:%(port)d'
string = stringfmt % data
structfmt = '>bh'
packetbytes = struct.pack(structfmt, 2, len(string))+string.encode('utf-16BE')
s.send(packetbytes)
connhash = s.recv(1024)
print("Connection Hash: " + connhash)
print('Sending data to http://session.minecraft.net/game/joinserver.jsp?user=' + usrnm + '&sessionId=' + mcsessionid + '&serverId=' + connhash + '...')
req = urllib.urlopen('http://session.minecraft.net/game/joinserver.jsp?user=' + usrnm + '&sessionId=' + mcsessionid + '&serverId=' + connhash)
returndata = req.read()
if(returndata == 'OK'):
print('session.minecraft.net says everything is okay, proceeding to send data to server.')
else:
print('Oops, something went wrong.')
time.sleep(5)
# All above here works perfectly.
enc_user = data['user'].encode('utf-16BE')
packfmt = '>bih{}shiibBB'.format(len(enc_user))
packetbytes = struct.pack(packfmt, 1, 23, len(data['user']), enc_user, 0, 0, 0, 0, 0, 0)
#This line is probably where something's going wrong:
packetbytes = struct.pack('>bih', 1, 23, len(data['user'])) + data['user'].encode('utf-16BE') + struct.pack('>hiibBB', 2,0,0,0,0,0)
print(len(packetbytes))
print('Sending ' + packetbytes + ' to server.')
s.send(packetbytes)
while True:
data = s.recv(1024)
if data:
print(data)
Yeah, you're sending the length of the string, which is the number of characters. Instead, you should be sending the number of bytes in the encoded string. Also, you should use "!" instead of ">" for clarity's sake, as "!" is used to indicate "network order", which this is. So this...
structfmt = '>bh'
packetbytes = struct.pack(structfmt, 2, len(string))+string.encode('utf-16BE')
... gets changed to this...
structfmt = '!bh'
encoded = string.encode('utf-16BE')
packetbytes = struct.pack(structfmt, 2, len(encoded))+encoded

DNS Client that is written Using C Sockets

I just want to write DNS client program using C sockets that
takes three arguments: a query name (e.g., host name or domain name) and a query type (A, or NS, or MX), and DNS server name. Print out the responses in the answer section of the DNS record received.
I know there is a command getaddrinfo..
but I just want to connect to lookup table and then
get the DNS server name...
so when i give the input ./a.out www.google.com A 144.20.190.70
it will show something similar to this:
Server: 144.20.190.70
Address: 144.20.190.70#53
Non-authoritative answer:
Name : www.google.com
Canonical name : www.l.google.com
Name : www.l.google.com
Address : 74.125.19.104
Name : www.l.google.com
Address : 74.125.19.105
Name : www.l.google.com
Address : 74.125.19.106
Name : www.l.google.com
Address : 74.125.19.147
Name : www.l.google.com
Address : 74.125.19.99
Name : www.l.google.com
Address : 74.125.19.103
Yes you need to see Bev.net.dns class that Rob-philpott made for .net
Click Here
building requests to send to DNS servers is not easy but once you can get the answer back from the server then you need to send it back to the browser and this is the bit i've got stuck on.
i listen on port 53/UDP and get the request and send it to the DNS server and get a valid responce but then i send that back to the browser using the remote client port as a UDP but the browser will not except the reply.
Robs code is real easy to use as shown below "Resolver.Lookup" and i just neded to add a bit so that the original byte array sent from the DNS server as saved in Resolver.Message ready to send back to the browser.
public void Listen()
{
receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp );
receiveEndPoint = new IPEndPoint(IPAddress.Any, receivePort); receiveSocket.Bind(receiveEndPoint);
receivePort = (receiveSocket.LocalEndPoint as IPEndPoint).Port;
receiveBuffer = new byte[BufferSize];
receiveAsyncResult = receiveSocket.BeginReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, ref receiveEndPoint, new AsyncCallback(NetworkMessageReceivedCallback), receiveSocket);
}
public void NetworkMessageReceivedCallback(IAsyncResult asyncResult)
{
EndPoint remoteEndPoint = null;
byte[] bytes = null;
remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); //Will contain the clients port
int bytesRead = receiveSocket.EndReceiveFrom(asyncResult, ref remoteEndPoint);
bytes = new Byte[bytesRead];
Buffer.BlockCopy(receiveBuffer, 0, bytes, 0, bytesRead);
//string ip = "208.67.222.222";
string ip = "192.168.1.254";
IPAddress dnsServer = IPAddress.Parse(ip);
Response R = Resolver.Lookup(bytes, dnsServer);
receiveSocket.SendTo(R.Message , remoteEndPoint);//127.0.0.1
receiveSocket.Close();
Listen();
}