Why does UDPSocket.send always call getaddrinfo in Ruby? - ruby

I just solved a latency issue in our infrastructure that was triggered because this code snippet here triggered a call to getaddrinfo on every run of the code:
sock = UDPSocket.open
sock.send("#{key}|#{value}", 0,
GRAPHITE_SERVER,
STATSD_PORT)
sock.close
Because we use statsd and graphite for high-volume event and stats monitoring, we were effectively triggering numerous calls getaddrinfo on every API call, and potentially tens of thousands every minute.
I modified this code to use the internal IP address, not the DNS name, of our graphite server, and was able to resolve the latency issue (presumably because the internal AWS VPC DNS server was not equipped to handle such a high volume of requests).
Now that my issue is resolved, I would love to know why the UDP implementation in Ruby is not using a cached IP address value (presumably based on the TTL of the domain name entry). Here is the relevant line and the function in full, you can see the call to rsock_addrinfo just at the end:
static VALUE
udp_send(int argc, VALUE *argv, VALUE sock)
{
VALUE flags, host, port;
struct udp_send_arg arg;
VALUE ret;
if (argc == 2 || argc == 3) {
return rsock_bsock_send(argc, argv, sock);
}
rb_scan_args(argc, argv, "4", &arg.sarg.mesg, &flags, &host, &port);
StringValue(arg.sarg.mesg);
GetOpenFile(sock, arg.fptr);
arg.sarg.fd = arg.fptr->fd;
arg.sarg.flags = NUM2INT(flags);
arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0);
ret = rb_ensure(udp_send_internal, (VALUE)&arg,
rsock_freeaddrinfo, (VALUE)arg.res);
if (!ret) rsock_sys_fail_host_port("sendto(2)", host, port);
return ret;
}
I assume this decision is intentional and would love to learn more about the reasons why.

getaddrinfo does not return data about the TTL... because it may not have it at all in fact, as the resolution may not necessarily be done over the DNS (could be hosts file, LDAP, etc. see /etc/nsswitch.conf)
From its manual here is the structure returned:
int getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res);
struct addrinfo {
int ai_flags; /* input flags */
int ai_family; /* protocol family for socket */
int ai_socktype; /* socket type */
int ai_protocol; /* protocol for socket */
socklen_t ai_addrlen; /* length of socket-address */
struct sockaddr *ai_addr; /* socket-address for socket */
char *ai_canonname; /* canonical name for service location */
struct addrinfo *ai_next; /* pointer to next in list */
};
After a successful call to getaddrinfo(), *res is a pointer to a linked list of one or more addrinfo structures.
So it is up to the thing "behind" getaddrinfo to do some caching or not, because getaddrinfo may have used the DNS to retrieve data, or not.
Some specific API for DNS, like getdnsapi will give back to the caller some information on the TTL, see https://getdnsapi.net/documentation/spec/ and example 6.2
6·2 Get IPv4 and IPv6 Addresses for a Domain Name
This example is similar to the previous one, except that it retrieves more information than just the addresses, so it traverses the replies_tree. In this case, it gets both the addresses and their TTLs.
Without any cache layer anywhere, since UDP is stateless, any new send must trigger resolution in some way or form.
You said:
"modified this code to use the internal IP address, not the DNS name"
You should instead install a local (on the box) recursive caching nameserver, such as unbound. All your local applications will benefit from it, and a faster DNS resolution (depending on how /etc/nsswitch.conf, /etc/resolv.conf and /etc/hosts are setup also).
For the associated bug report hinted by #Casper it seems at its core more an issue about IPv6 vs IPv4 which could be solved either by adjusting /etc/gai.conf or equivalent or doing some more clever programming around opening the connection, with the so called "happy eyeball algorithm" where you try to resolve both A and AAAA at the same time which means two parallel DNS queries (because you can not combine them into one per the protocol) and try to use the fastest one coming back, with a slight preference for AAAA if you want to be in the modern camp so you would fire the A one only some given amount of milliseconds after the AAAA to catch the case where you do not get a reply at all for AAAA or a negative one. See RFC6555 for details.

Related

CopyPipe of DriverKit IOUSBHostInterface fails with kIOReturnError (0xe00002bc)

For my own edification, I'm trying to read some audio data from a USB audio interface using a DriverKit System Extension.
My IOProviderClass is IOUSBHostInterface. I can successfully Open() the interface, but CopyPipe() returns kIOReturnError (0xe00002bc). Why can't I copy the pipe?
To be able to open the interface at all, I had to outmatch AppleUSBAudio so my IOKitPersonalities explicitly match the bConfigurationValue, bInterfaceNumber, idVendor, idProduct, and bcdDevice keys. This list may not be minimal.
In ioreg I can normally see the interfaces (sometimes only my matching one is there, although I think this is a degenerate situation). I see a AppleUserUSBHostHIDDevice child on some of my other interfaces. Could this be the problem? Normally the device has no problem being both USBAudio and HID. I am trying unsuccessfully to out match HID too.
I was passing the wrong endpoint address to CopyPipe().
To find an endpoint address you need to enumerate through the IOUSBDescriptorHeaders in the IOUSBConfigurationDescriptor and examine the descriptors with bDescriptorType equal to kIOUSBDescriptorTypeEndpoint.
IOUSBGetNextDescriptor() from USBDriverKit/AppleUSBDescriptorParsing.h is made for this and will save you from having think about pointer manipulation.
If the endpoint is in a different alternate setting, then you need to switch the interface to that one with SelectAlternateSetting().
void
enumerate_configs(const IOUSBConfigurationDescriptor *configDesc) {
const IOUSBDescriptorHeader *curHeader = NULL;
while ((curHeader = IOUSBGetNextDescriptor(configDesc, curHeader))) {
switch (curHeader->bDescriptorType) {
case kIOUSBDescriptorTypeEndpoint: {
auto endpoint = (const IOUSBEndpointDescriptor *)curHeader;
os_log(OS_LOG_DEFAULT, "Endpoint bLength: %{public}i, bDescriptorType: %i, bEndpointAddress: %i, bmAttributes: 0x%x, wMaxPacketSize: %i, bInterval: %i",
endpoint->bLength,
endpoint->bDescriptorType,
endpoint->bEndpointAddress, // pass this to CopyPipe()
endpoint->bmAttributes,
endpoint->wMaxPacketSize,
endpoint->bInterval);
}
break;
default:
os_log(OS_LOG_DEFAULT, "some other type: %{public}i", curHeader->bDescriptorType);
break;
}
}
}

zmq.error.ZMQError: Cannot assign requested address

I have the following pull - publisher ZMQ schema on an Amazon EC2 machine:
I am working with the Public IP address of my EC2 Amazon machine.
I am trying send data via ZMQ PUSH socket from the client side to ZMQ PULL socket server side, which is this:
import zmq
from zmq.log.handlers import PUBHandler
import logging
# from zmq.asyncio import Context
def main():
ctx = zmq.Context()
publisher = ctx.socket(zmq.PUB)
# publisher.bind("tcp://*:5557")
publisher.bind("tcp://54.89.25.43:5557")
handler = PUBHandler(publisher)
logger = logging.getLogger()
logger.addHandler(handler)
print("Network Manager CNVSS Broker listening")
collector = ctx.socket(zmq.PULL)
# collector.bind("tcp://*:5558")
collector.bind("tcp://54.89.25.43:5558")
while True:
message = collector.recv()
print("Publishing update %s" % message)
publisher.send(message)
if __name__ == '__main__':
main()
But when I excute this script, I get this error:
(cnvss_nm) ubuntu#ip-172-31-55-72:~/cnvss_nm$ python pull_pub-nm.py
Traceback (most recent call last):
File "pull_pub-nm.py", line 28, in <module>
main()
File "pull_pub-nm.py", line 10, in main
publisher.bind("tcp://54.89.25.43:5557")
File "zmq/backend/cython/socket.pyx", line 547, in zmq.backend.cython.socket.Socket.bind
File "zmq/backend/cython/checkrc.pxd", line 25, in zmq.backend.cython.checkrc._check_rc
zmq.error.ZMQError: Cannot assign requested address
(cnvss_nm) ubuntu#ip-172-31-55-72:~/cnvss_nm$
I've changed my IP-address to publisher.bind("tcp://*:5557") and collector.bind("tcp://*:5558") in the server side, and my script is running:
(cnvss_nm) ubuntu#ip-x-x-x-x:~/cnvss_nm$ python pull_pub-nm.py
Network Manager CNVSS Broker listening
But from my client-side code ( added recently ), any data is sent.
#include <zmq.hpp>
#include <zmq.h>
#include <iostream>
#include "zhelpers.hpp"
using namespace std;
int main(int argc, char *argv[])
{
zmq::context_t context(1);
/*
std::cout << "Sending message to NM Server…\n" << std::endl; */
zmq::socket_t subscriber(context, ZMQ_SUB);
subscriber.connect("tcp://localhost:5557");
subscriber.setsockopt(ZMQ_SUBSCRIBE, "", 0);
zmq::socket_t sender(context, ZMQ_PUSH);
sender.connect("tcp://localhost:5558");
string firstMessage = "Hola, soy el cliente 1";
while (1)
{
// Wait for next request from client
std::string string = s_recv(subscriber);
std::cout << "Received request: " << string << std::endl;
// Do some 'work'
// sleep(1);
// Send reply back to client
// zmq::message_t message(firstMessage.size() + 1);
// Cualquiera de los dos se puede
// memcpy(message.data(), firstMessage.c_str(), firstMessage.size() + 1);
// s_send(sender, "Hola soy un responder 1");
// sender.send(message);
}
}
I think that my inconvenient is on my EC2 machine network configuration or on the way of setup the IP address of the server.
When I test the clients and server locally, all it works perfectly.
Is there any possibility of performing some forwarding or NAT operation on my EC2 machine?
My clients do not reach the server.
I have the security groups rule the above mentioned ports 5557 and 5558.
How to solve this inconvenience?
I had a similar situation where I was using ZMQ on EC2 and getting "Cannot assign requested address." I was also using Elastic IP as suggested in the answer, but it did not work for me. It turned out that on EC2, the sending side (ZMQ.PUSH) needs to bind to the private IP rather than to the public, while the receiving side needs to bind to the public IP, so trying to bind the server to Elastic IP caused the error. After I changed it to bind the server ZMQ.PUSH side to Private IP and the client ZMQ.PULL to Elastic IP (on the same port), it worked.
How to solve this inconvenience?
1 )If in doubts about the EC2 addresses, first try to test the reversed .bind() / .connect(), so that the EC2-side localhost address assignments are out f the game, and your connectivity proof towards a known IP-address will not depend on the EC2-side settings.
2 )Next, given there are no details about the client-side part of the MCVE, I may have got the scenario idea incorectly, so bear with me - there are only these compatible ZeroMQ Scalable Formal Communication Archetype sockets' matches available ever since, up to API v4.2.x in 2018/Q2:
{ PUB: [ SUB,
XSUB,
None
],
PULL: [ PUSH,
None
],
...
}
3 )There is a good engineering practice not to let unhandled exceptions happen, the more, if Context()-instance may still bear the possession of IP:PORT# (b)locked resource ( sometimes even beyond the python process termination ( many incidents with my own naive and this way deadlocked experiments in my past dark history :o) )
Each step in the infrastructure setup ought be wrapped into error-handling syntax-clause, best including a finally: section, where so far created resources will occasionally get dismantled in a graceful manner in cases, when exception(s) spring out. This way your code will prevent a forever hanging orphan(s), that have just an option to reboot the platform so as to get rid of these, otherwise impossible to salvage, hostages.
Problem solved,a final summary :
The initially indicated problem ( diagnosed at .bind() / .connect() phase ) was, as depicted earlier, related to Amazon EC2 instance IP-address mapping, as the term, needed for any transport-class Endpoint setup, localhost:port#
camdebu on Nov 1, 2012 5:07 PM explained all the steps needed:Setup an Elastic IP to your EC2 isntance. You will then have a static IP address. There's no cost for the Elastic IP as long as you have it pointed to an EC2 instance.
You should then have no problem connecting to your new IP Address and port as long as your security group is setup correctly.
-Cam-
Check your Security Group Rule. Make sure you allow the port to communicate from outside the instance. (Enable All TCP and Check). [ added Yesu Jeya Bensh.P ]
The recently posted client-code but shows another issue, a mutual block, generated by a non-cooperating zmq::socket_t sender( context, ZMQ_PUSH ), which actually never sends a single message.
Given the client goes into while(1)-loop as posted above, the associated peer will inadvertently get into an unsalvageable blocked state inside the python-made main(), since :
def main():
...
collector = ctx.socket( zmq.PULL )
#ollector.bind( "tcp://*:5558" )
collector.bind( "tcp://54.89.25.43:5558" )
while True:
message = collector.recv() # THIS SLOC WILL BLOCK FOREVER HERE,
... # GIVEN <sender> NEVER SENDS...
so more care is to be taken, so as to make the flow of events robust enough, not to ever fall into this or similar unsalvageable mutual block.

How does send() work in omnet++

Does the send() in omnet++ set the source address of the packet to the current host address?
Why am I asking? because I'm trying to code a class for a malicious host "Eve" that performs a replay attack.
void MalAODVRouter::handleMessage(cMessage *msg)
{
cMessage *ReplayMsg = msg->dup();
AODVRouting::handleMessage(msg);
capturedMsgs++;
if (capturedMsgs==10) // One out of every 10 packets (frequency of replay)
{
//we can add a delay before sending the copy of the message again (1 time unit)
sendDelayed(ReplayMsg, 1,"ipOut");
ReplayedMsgs++;
std::cout<<"Launched Replay Packet!\n";
ev<<"Launched Replay Packet!\n";
this->capturedMsgs=0;
// }
}
}
You can see at the beginning of my code snippet I tried using the function dup() to duplicate a packet (msg) Eve's receives while its on it's on its way to the legitimate destination.
Now, can I send the duplicated packet later and it would be having the original source address OR should I dig deeper into layers to fake the source address to have Bob's address instead of Eve's? like below:
/*UDPPacket *udpPacket = dynamic_cast<UDPPacket *>(msg);
AODVControlPacket *ctrlPacket = check_and_cast<AODVControlPacket *>(udpPacket->decapsulate());
IPv4ControlInfo *udpProtocolCtrlInfo = dynamic_cast<IPv4ControlInfo *>(udpPacket->getControlInfo());
ASSERT(udpProtocolCtrlInfo != NULL);
IPv4Address sourceAddr = udpProtocolCtrlInfo->getSrcAddr(); //get Source Address
IPv4Address destinationAddr = udpProtocolCtrlInfo->getDestAddr(); //get Destination Address
IPv4Address addr = getSelfIPAddress();
if (addr != destinationAddr) // if it is not destined for "Eve"
{
UDPPacket *ReplayUDPPacket = udpPacket;
AODVControlPacket *ReplayCtrlPacket = check_and_cast<AODVControlPacket *>(ReplayUDPPacket->decapsulate());
IPv4ControlInfo *ReplayUDPProtocolCtrlInfo = dynamic_cast<IPv4ControlInfo *>(ReplayUDPPacket->getControlInfo());
ASSERT(ReplayUDPProtocolCtrlInfo != NULL);
ReplayUDPProtocolCtrlInfo->setSrcAddr(sourceAddr); //Forge Source
ReplayUDPProtocolCtrlInfo->setDestAddr(destinationAddr); //Keep Destination
*/
//we can add a delay before sending the copy of the message again (1 time unit)
sendDelayed(ReplayMsg, 1,"ipOut");
ReplayedMsgs++;
std::cout<<"Launched Replay Packet!\n";
ev<<"Launched Replay Packet!\n";
this->capturedMsgs=0;
Does the send() method automatically sets the source address of the outgoing packet to the current host address? If so, then my replay attempt is not working...
send() is an OMNeT++ API call. As OMNeT++ is just a generic discrete event simulation framework, it does not know anything about the model code (so it cannot and should not manipulate it). IP address is a defined in the INET framework so only code from the INET framework can change it.
On the other hand the modules in the standard host below you module can do whatever they want before the packet is sent out to the network. Now in this actual case, the source IP address is determined by the control info that is attached to the packet. dup()-ing the packet copies that information too, so the IP address will be the same.

Linux kernel network device driver and skb pointers

I am writing a network device driver.
Kernel 2.6.35.12
The device is supposed to be working when it is connected to a bridge port.
I am trying to intercept ICMPv6 RA and NS messages (Router/ Neighbor solicitation) forwarded to the interface from the bridge.
eth <–> br0 <–> mydevice
In the device start_xmit function I am doing to following:
Check that the protocol field after the Ethernet header is IPV6 (0x86dd)
Check that the ipv6 next header is ICMPv6 and check its type:
__u8 nexthdr = ipv6_hdr(skb)->nexthdr;
if (nexthdr == htons (IPPROTO_ICMPV6))
{
struct icmp6hdr *hdr = icmp6_hdr(skb);
u8 type = hdr->icmp6_type;
if(type == htons (NDISC_NEIGHBOUR_SOLICITATION) || type == htons (NDISC_ROUTER_SOLICITATION))
{
….Do something here…
}
}
When RS/NS are sent from within the device (e.g br0), I see that the code is working right.
The problem is when traffic is forwarded through the bridge from the other port.
I see that the icmp6_hdr(skb) returns an incorrect header.
Debugging some more, it seems that the
skb->network_header and the skb->transport_header are pointing to the same place.
icmp6_hdr is using the transport_header which explain why it is incorrect.
Dumping the skb data it looks that all the headers and payload are at the right offset (also compared it with tcpdump)
I suspect that it might be related to the bridge code, before going to dive into it,
I thought that maybe anyone had come up against anything similar or have any other ideas?
Part of the problem is that you are assuming that Netfilter did anything more than just figure out what was the next header. In my experience (albeit not very long) you want to do something like this:
struct icmp6hdr *icmp6;
// Obviously don't do this unless you check to make sure that it's the right protocol
struct ipv6_hdr *ip6hdr = (struct ipv6_hdr*)skb->network_header;
// You need to move the headers around
// Notice the memory address of skb->data and skb->network_header are the same
// that means that the IP header hasn't been "pulled"
skb->transport_header = skb_pull(skb, sizeof(struct ipv6_hdr));
if(ntohs(ip6hdr->nexthdr) == IPPROTO_ICMPV6) {
icmp6 = (struct icmp6hdr*)skb->transport_header;
// Doing this is more efficient, since you only are calling the
// Network to Host function once
__u8 type = ntohs(hdr->icmp6_type);
switch(type) {
case NDISC_NEIGHBOUR_SOLICITATION:
case NDISC_ROUTER_SOLICITATION:
// Do your stuff
break;
}
}
Hopefully this was helpful. I just started diving into writing Netfilter code, so I am not exactly certain 100%, but I found this out when I was trying to do something similar with IPv4 on the NF_IP_LOCAL_IN hook.

Sockets - sending more data after reciving answer from server

I have a client/server application.
The client sends a question to the server and receives an answer.
This works great - but when I'm trying to use the same socket again to send another question (without closing the socket - after receiving an answer) the server doesn't get the second question.
Here's the code for sending and receiving answer (this should work in a loop of some-sort):
char* buf = "GET /count.htm HTTP/1.1\r\nHost: 127.0.0.1:666\r\nAccept: text/html,application/xhtml+xml\r\nAccept-Language: en-us\r\nAccept-Encoding: gzip, deflate\r\nUser-Agent: Mozilla/5.0\r\n\r\n";
int nBytesToSend= strlen(buf);
int iPos=0;
while(nBytesToSend)
{
int nSent=send(hClientSocket,buf,nBytesToSend,0);
assert(nSent!=SOCKET_ERROR);
nBytesToSend-=nSent;
iPos+=nSent;
}
//prepare buffer for incoming data
char serverBuff[256];
int nLeft=sizeof(serverBuff);
iPos=0;
do //loop till there are no more data
{
int nNumBytes=recv(hClientSocket,serverBuff+iPos,nLeft,0);
//check if cleint closed connection
if(!nNumBytes)
break;
assert(nNumBytes!=SOCKET_ERROR);
//update free space and pointer to next byte
nLeft-=nNumBytes;
iPos+=nNumBytes;
}while(1);
With this code it is impossible that you can ever send the second question, as you can never get out off the loop that reads the reply (except when you get a segmentation violation because your offset overflows the buffer, or when the peer closes the connection, in which case you can't send and he can't receive either).
And just asserting the absence of an error is never adequate. If you get an error you need to see what was and react accordingly. At the least you need to print it.

Resources