I'm writing my own DNS black hole to block advertising and malware on my home network. I realize that this type of program already exists, but I would like to learn the process and write my own. From the Linux documentation, it appears that sendmsg() can be told to use a different return address so that a UDP packet can be forwarded and the receiving server will send the response to the original requester instead of my server. From the sparse documentation, I set up my socket bound to port 53 (DNS). I'm receiving DNS requests, interpreting them and responding when the site is blacklisted. For 'good' domain names, I'm getting 2 different results. On MacOS my forwarding request is sent, but the response comes back to my program instead of the original requester. On Linux (Armbian 4.13 kernel), I get EINVAL from the sendmsg() call and nothing is sent. Can anyone shed some light on what I'm doing wrong? (all error checking has been removed for brevity)
Additional info... a wrinkle in this scheme is that the original DNS request is bound to a socket port other than 53 (obviously). How do I tell sendmsg() to return the response to the correct port number of the original request?
My 'proxy' version of this works. I store the transaction ID and requesting port number of the original request and am able to return the response successfully
Latest news - I was able to successfully accomplish what I wanted, but not with sendmsg(). By using RAW sockets and 'spoofing' the return address in the IP header, I was able to get the packet sent back to a different return address and port number (using sendto). nslookup sees this as a problem, but browsers are okay with it. I guess we can call this 'case closed'
struct sockaddr_in addr, addrfrom, fwaddr;
socklen_t addrLen = sizeof(struct sockaddr_in);
int rc, listen_sock;
listen_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
rc = 1;
setsockopt(listen_sock, IPPROTO_IP, IP_PKTINFO, &rc, sizeof(rc));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(53); // port address of DNS
addr.sin_addr.s_addr = INADDR_ANY;
bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
memset(&fwaddr, 0, sizeof(fwaddr));
fwaddr.sin_family = AF_INET;
fwaddr.sin_port = htons(53);
fwaddr.sin_addr.s_addr = 0x08080808; // Google DNS server
<... Receive DNS request from client...>
if (bForward)
{
struct msghdr msg;
struct iovec iov[1];
struct {
struct cmsghdr cm; /* this ensures alignment */
struct in_pktinfo ipi;
} cmsg;
memset(&cmsg, 0, sizeof(cmsg));
memset(&msg, 0, sizeof(msg));
iov[0].iov_base = request_bufffer;
iov[0].iov_len = request_len;
msg.msg_flags = 0;
msg.msg_name = &fwaddr; // dest address of packet
msg.msg_namelen = addrLen;
msg.msg_iov = &iov[0];
msg.msg_iovlen = 1;
msg.msg_control = &cmsg;
msg.msg_controllen = sizeof(cmsg); //sizeof(struct in_pktinfo);
cmsg.cm.cmsg_len = sizeof(cmsg);
cmsg.cm.cmsg_level = IPPROTO_IP;
cmsg.cm.cmsg_type = IP_PKTINFO;
cmsg.ipi.ipi_ifindex = 0;
cmsg.ipi.ipi_spec_dst = addrfrom.sin_addr; // original source address
rc= sendmsg(listen_sock, &msg, 0);
} // bForward
Issues
I see 2 potential issues in this code.
You are not using the CMSG macros to allocate and manipulate your ancillary data (i.e. your cmsg structure).
Even if that is fixed, you are trying to spoof the source IP address for the sent packet - i.e. you are trying to declare it is from an address not owned by this box. I have never tried to do this but wouldn't be surprised if you find the kernel security prevents you from doing that.
So what to do?
First I would fix up your meassage structures, using code like this SO answer. I suspect you should look closely at the offset of in_pktinfo and the length you're passing in to cmsg_len as these could cause EINVAL errors. A possible strategy might be to cut the code back to the point it's just sending packets normally to see which data structures are being rejected.
If that doesn't do it, I guess you could then consider re-writing your send logic to use raw sockets to bypass any IP validation your kernel might be doing. For example, this code looks like it should be roughly what you need.
If that's still not enough, I suspect you need to route around in the kernel security systems.
Related
I got the UDP communication up and running.
In a simple striped down version the client is basically doing this: (Yes, there are a lot of ifs and error checking in the code and No, without a lot of definitions it will not work as is. But I am more on the conseptual here than actually code)
sock = socket(AF_INET, SOCK_DGRAM, 0);
server_length = sizeof(struct sockaddr_in);
sendto(sock, "Hi", 3, 0, (struct sockaddr *)&myaddr, server_length);
numRes = recvfrom(sock, (char *)buff, (int)sizeof(buff), 0, (struct sockaddr *)&myaddr, &server_length);
And the server is basically the same:
sock=socket(AF_INET, SOCK_DGRAM, 0);
bind(sock,(struct sockaddr *)&server,length);
resBytes=recvfrom(sock,buff,(int)sizeof(buff),0,(struct sockaddr *)&from, &fromlen);
sendto(sock, "Test",5, 0, (struct sockaddr *)&from, fromlen);
This works. Both ends receive the data the other end sends.
Here is my problem: The server should be handling e lot of requests at the same time. I made a queue for the messages so that another thread could handle them, before I send them back to the client in yet another thread. When I do this I created a new socket for the sending, used the same "from" address that follows the message, but the client never receive it.
Is it correct to understand that replying on a UDP message has to be done with the same socket?
If I use the same socket for sending and receiving, what happens if I have three clients sending in messages, then after those are processed I will answer them in a random order. Will this work?
I can make a "server" on the client, but my guess is that any NAT would kill that idea fast.
What I try to attempt is more or less this:
sock=socket(AF_INET, SOCK_DGRAM, 0);
sock2=socket(AF_INET, SOCK_DGRAM, 0);
bind(sock,(struct sockaddr *)&server,length);
resBytes=recvfrom(sock,buff,(int)sizeof(buff),0,(struct sockaddr *)&from, &fromlen);
sendto(sock2, "Test",5, 0, (struct sockaddr *)&from, fromlen);
If this kind of code should work, I will go back to my debugging, but right now it seems like this is not the case.
Edit:
Someone added "Multithreading" as a tag. Forget the multithread thing Its more to explain why I don't respond promptebly to the same request. I can have all the communication in one thread if thats whats needed. Sorry if this confuse people, but my main question here is: a) Can I send response down another socked without defining a "server" on the client. b) If not, how will it handle random order of reply on the same socked (IE Client A sends, Client B sends, answer to Client B, then to Client A)
It is possible for a single UDP socket to receive data from multiple sources and to send data to multiple peers, as long as the socket is not connected. This means if you explicitly or implicitly bind the socket and then use sendto and recvfrom (or recv) then you can use the same socket to send and receive data from multiple peers. But, if you first connect the socket then you will only be able to receive data from a socket which is bound to the connected ip:port, i.e. usually the same socket which received the data.
To test this you can use a simply Python server (would work the same with C). This server creates two sockets: sock1 and sock2. It will receive a message on sock1 and then send a message back using first sock2 (the one which did not receive the original message) and then sock1 (the one which received the original message):
from socket import *
sock1 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
sock2 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
sock1.bind(('127.0.0.1',9999))
data,addr = sock1.recvfrom(1024)
print("got '{}' from {}".format(data,addr))
sock2.sendto(b"send from sock2", addr)
sock1.sendto(b"send from sock1", addr)
As the peer we have a client which depending on the configuration uses a connected socket (i.e. connect + send) or an unconnected socket (i.e. no connect and sendto):
from socket import *
connected=False
sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)
if connected:
sock.connect(('127.0.0.1',9999))
sock.send('send from connected client')
else:
sock.sendto('send from unconnected client',('127.0.0.1',9999))
while True:
data,addr = sock.recvfrom(1024)
print("got '{}' from {}".format(data,addr))
If you run the client with the connected socket (connected=True) you'll see that it will only get the message from sock1 back, because sock1 is (explicitly) bound to the ip:port the client is connected to and sock2 is (implicitly) bound to some other address:
got 'send from sock1' from ('127.0.0.1', 9999)
If instead the client is using an unconnected socket (connected=False) then it will receive both the messages from sock1 and sock2:
got 'send from sock2' from ('127.0.0.1', 44596)
got 'send from sock1' from ('127.0.0.1', 9999)
I need only my global IPv6 address and not local link address. I have set ai_flags to AI_ADDRCONFIG as mentioned in msdn. But GetAddrInfoW returns both global and local link addresses. Is there any way i can find out from ADDRINFOW result structure the type of address?
std::wstring whostname = L"hostname";
ADDRINFOW hints;
memset(&hints, 0, sizeof(ADDRINFOW));
hints.ai_family = AF_UNSPEC; // IPv4 and IPv6
hints.ai_socktype = SOCK_STREAM; // TCP only, no UDP
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_ADDRCONFIG; // Get only global IPv6 address
ADDRINFOW *list = NULL;
result = GetAddrInfoW(
whostname.c_str(),
NULL,
&hints,
&list
);
I guess there isnt something that the API provides to get only the global IPv6 address. I have managed to identify the global address based on the IPv6 address format. From Wiki, The scope can be used to identify Link-local address.
I followed the following tutorial: http://www.pubnub.com/blog/pubnub-streaming-texas-instruments-iot/
step by step and I managed to compile and code and connect to my Wi-Fi access point.
I think I managed to connect to PubNub (the code prints on the terminal screen "PubNub Set Up" but in the code there is no real verification that it was indeed set up.
I opened an account on PubNub and I named my channel "testing" (I named it the same in the code I uploaded - I checked that a million times) and when I go to the Dev Console and click on subscribe I can't see anything! I mean I can post messages through the Dev Console but what I really want to see are the messages from the CC3100.
I checked the UART terminal on my computer and I see the data being printed constantly so I know it is working.
I went over the tutorial again and again and I'm doing the same thing but it just doesn't work.
Any help would be appreciated!
What am I missing?
Thanks
First to verify your PubNub account is properly configured and your local Wi-Fi connectivity is working - are you able to publish messages from the dev console in one browser and receive them in the dev console on another browser? (both using the same channel name, of course). If that works, please send a message to help (at) pubnub (dot) com with your sub-key info and information about your project and we will try to assist you tracking down the issue.
This answer is posted really late. I admit I forgot about this post so I just decided to update it (a few years late though).
I started digging to try and see what was the problem and I think I found it. First of all, I saw that PubNub.publish() wasn't working properly with the json_String because the json_String was 90% gibrish. So I erased most of the code that constructed the json_String (the part that inserts the analog values) and made it simpler.
I then also added a part of code at the end which was needed for proper performance of the client variable which I got off of a part of code which was used for an arduino based project using the CC3100.
Anyway, the new code is the one below and now it works FINE! I finally see all the input streaming on PubNub! Thanks a lot! :D
/*PubNub sample JSON-parsing client with WiFi support
This combines two sketches: the PubNubJson example of PubNub library
and the WifiWebClientRepeating example of the WiFi library.
This sample client will properly parse JSON-encoded PubNub subscription
replies using the aJson library. It will send a simple message, then
properly parsing and inspecting a subscription message received back.
This is achieved by integration with the aJson library. You will need
a version featuring Wiring Stream integration, that can be found
at http://github.com/pasky/aJson as of 2013-05-30.
Please refer to the PubNubJson example description for some important
notes, especially regarding memory saving on Arduino Uno/Duemilanove.
You can also save some RAM by not using WiFi password protection.
created 30 May 2013
by Petr Baudis
https://github.com/pubnub/pubnub-api/tree/master/arduino
This code is in the public domain.
*/
#include <SPI.h>
#include <WiFi.h>
#include <PubNub.h>
#include <aJSON.h>
static char ssid[] = "NetSSID_Name"; // your network SSID (name)
static char pass[] = "NetworkdPassword"; // your network password
static int keyIndex = 0; // your network key Index number (needed only for WEP)
const static char pubkey[] = "pub-c-51eb45ec-b647-44da-b2aa-9bf6b0b98705";
const static char subkey[] = "sub-c-7e78ed9c-991d-11e4-9946-02ee2ddab7fe";
const static char channel[] = "testing";
#define NUM_CHANNELS 4 // How many analog channels do you want to read?
const static uint8_t analog_pins[] = {23, 24, 25, 26}; // which pins are you reading?
void setup()
{
Serial.begin(9600);
Serial.println("Start WiFi");
WiFi.begin(ssid, pass);
while(WiFi.localIP() == INADDR_NONE) {
Serial.print(".");
delay(300);
}
Serial.println("WiFi set up");
PubNub.begin(pubkey, subkey);
Serial.println("PubNub set up");
delay(5000);
}
void loop()
{
WiFiClient *client;
// create JSON objects
aJsonObject *msg, *analogReadings;
msg = aJson.createObject();
aJson.addItemToObject(msg, "analogReadings", analogReadings = aJson.createObject());
// get latest sensor values then add to JSON message
/*for (int i = 0; i < NUM_CHANNELS; i++) {
String analogChannel = String(analog_pins[i]);
char charBuf[analogChannel.length()+1];
analogChannel.toCharArray(charBuf, analogChannel.length()+1);
int analogValues = analogRead(analog_pins[i]);
aJson.addNumberToObject(analogReadings, charBuf, analogValues);
}*/
// convert JSON object into char array, then delete JSON object
char *json_String = aJson.print(msg);
aJson.deleteItem(msg);
// publish JSON formatted char array to PubNub
Serial.print("publishing a message: ");
Serial.println(json_String);
Serial.println(channel);
client = PubNub.publish(channel, json_String);
Serial.println(*client);
free(json_String);
if (!client) {
Serial.println("publishing error");
delay(1000);
return;
}
client->stop();
delay(500);
}
//- See more at: http://www.pubnub.com/blog/pubnub-streaming-texas-instruments-iot/#sthash.tbQXMIzw.dpuf
This is a C++ server application which communicates with all clients based on UDP protocol. When a user logs into the server from client, the client application registers a UDP channel to the server and this channel is in fixed format: IP+Port, which means if the IP keeps unchanged, then no matter what user logged in the client registers a same channel.
The server's socket layer maintains a heartbeat mechanism which will remove the channel if it doesn't receive any heartbeat packets from the channel in 3 minutes. Everything works fine until the client is down, e.g. the network wire is plugged off. Look at below scene:
1. User-A logs into server. The Client registers channel (IP:Port)
to the server. Because the UDP channel is alive, so the Server
sets the User status of User-A as Online.
2. Kill the client process, and within 3 minutes(before the channel
timeouts in server), let User-B logs into server from the same
computer. Because the IP remains unchanged, so actually the client
registers a same (IP:PORT) pair to the server as it did when User-A
logs in.
3. Since the Server receives packets from (IP:PORT), so it considers
User-A is still alive, thus setting the user status of User-A as
Online which is not right anymore.
In above scenario, the Server is not able distinguish different users logged from a same computer, which results in wrong user states. Does anybody know how to solve this problem?
I see no reason to presume that the origin port number for any two users will be identical unless the client application is explicitly binding the UDP socket. Clients which initiate the communication can often use ephemeral ports just as effectively. Ephemeral ports may or may not be sufficiently random for your particular use case, code below shows how to access client ports from inbound UDP data. If they are not sufficiently random, it may be wise to encode session cookies or user-cookies into the protocol.
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
int
main() {
/*- setup UDP socket on 8500 */
int rc;
int server_socket;
struct sockaddr_in server_address;
server_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (server_socket < 0) {
perror("failed to init socket");
return 1;
}
memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_port = htons(8500);
server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
rc = bind(server_socket, (struct sockaddr*) &server_address
, sizeof(server_address));
if (rc < 0) {
perror("failed to bind");
return 2;
}
/* - receive from UDP socket and print out origin port - */
char buffer[4096];
struct sockaddr_in client_address;
int client_address_len = sizeof(client_address);
rc = recvfrom(server_socket
, buffer
, sizeof(buffer)
, 0
, (struct sockaddr*) &client_address
, &client_address_len);
if (rc < 0)
return 3;
fprintf(stderr, "%s %d\n", buffer, ntohs(client_address.sin_port));
return 0;
}
I see following behavior with sendmsg in case of IPv4:
Suppose that 10.1.2.3 is the client IP.
And 10.1.2.10 is configured on one of the interfaces of client.
In an UDP message, following control information is added into the packet:
It is just the source-address or interface address that server should use in replying back to the client:
cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(sa->sin_addr);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_SENDSRCADDR_WITH_ERROR;
* (struct in_addr *)CMSG_DATA(cmsg) = sa->sin_addr;
cmsg = (struct cmsghdr *)((caddr_t) cmsg + ALIGN(cmsg->cmsg_len));
And message is sent as:
sendmsg(fd, send_msg, 0);
If I configure 10.1.2.10 as source-ip and once it is added into cmsg, things work fine.
server replies back to 10.1.2.10.
But, if I configure some un-reachable IP address or IP that is not configured on any interface on the client, sendmsg fails with below error:
sendmsg to 10.1.2.3(10.1.2.3).1813 failed: Can't assign
requested address
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
But I do not see the same behavior with IPv6:
Suppose that 2001::1 is the client IP.
And 2001::2001 is configured on one of the interfaces of client.
IPv6 source address is added into control message as below:
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
memcpy((struct in6_addr *)CMSG_DATA(cmsg), &(sa6->sin6_addr),
sizeof(sa6->sin6_addr));
cmsg = (struct cmsghdr *)((caddr_t) cmsg + ALIGN(cmsg->cmsg_len));
It works fine, if I configure 2001::2001 as source-ip and server does reply back to this address.
But If I configure an unreachable IPv6 source address say 1001::1001, there is no error message from sendmsg similar to the one we see in IPv4 case. Message is still sent with original IPv6 which is 2001::1.
Can someone please suggest on what can be the problem?
Thanks.
IP_SENDSRCADDR and IPV6_PKTINFO must be two different implementations. Maybe in the first case it just control errors. Have you tried to set the interface index in the ancillary data for IPV6_PKTINFO? For IPV6_PKTINFO the ancillary data is of type: in6_pktinfo.
struct in6_pktinfo {
struct in6_addr ipi6_addr; /* src/dst IPv6 address */
unsigned int ipi6_ifindex; /* send/recv if index */
};
Hope this helps in some way
I meet the same issue, I set source address as 408:6666:f:f500::1 (not local IP), but I received the packet with 4085:6666:f:fc10::1 (the local IP) as source address, no matter I set ipi6_ifindex or not.
I will get forward to investigate it.