NTP Server behind NAT - c

I have a self implemented NTP server working on a Ubuntu machine.
NTP Client is another Ubuntu machine with both ntpdate and ntpd utilities installed. Server has has address 192.168.2.200 and the client is 192.168.2.74 . If I run:
ntpdate 192.168.2.200
Or
ntpd -c /root/NTP.conf
With NTP.conf
server 192.168.2.200 minpoll 0 maxpoll 0 prefer
The client OS will synchronize with my server.
Next step is to put server behind a NAT.
NTP Client still has address 192.168.2.74 but now the server is behind a Windows 10 machine with address 192.168.2.75 .
I set the port forwarding in order to forward UDP packets directed to 192.168.2.75:123 to the internal address 192.168.2.200:123, where the server is located. For some reason, even if the client (2.74) receives the same answer from the server (2.200 natted as 2.75), it won't synchronize.
This is what I've done so far:
Define a custom struct for NTP message as defined in rfc5905:
typedef struct
{
uint8_t hdrhead[4];
uint32_t delay;
uint32_t dispersion;
uint32_t reference_id;
uint64_t reference_ts;
uint64_t origin_ts;
uint64_t receive_ts;
uint64_t transmit_ts;
} ntp_msg5905;
In main function, I have a simple while loop:
// socket initialization...
while (1)
{
uint8_t recv_msg[NTP_MSGLEN];
while (recvfrom(s, recv_msg, NTP_MSGLEN, 0, (struct sockaddr*) &src_addr, &src_addrlen) < NTP_MSGLEN)
{
_log_("Arrived wrong NTP request: message too short", stdout);
}
_log_("Arrived NTP request", stdout);
uint64_t recvtime;
timestamp_64(&recvtime);
ntp_reply(s, &src_addr , src_addrlen, *(ntp_msg5905*)recv_msg, recvtime);
}
timestamp_64() function just build a 64bit timestamp obtained with gettimeofday(), nothing special or ntp related.
The ntp_reply function is:
void ntp_reply(int clisock, struct sockaddr_in* saddr_p, socklen_t saddrlen, ntp_msg5905 request, uint64_t recvtime)
{
ntp_msg5905 reply;
memset(&reply, 0x00, NTP_MSGLEN);
reply.hdrhead[STRATUM] = NTP_STRATUM_PRIMARY_SERVER;
reply.hdrhead[PREC] = NTP_HOST_PRECISION;
reply.delay = NTP_NODELAY;
reply.dispersion = NTP_NODISPERSION;
reply.reference_id = NTP_REF_ID_GPS;
reply.hdrhead[LIVN] = request.hdrhead[LIVN] & 0x38 + NTP_MODE_SERVER;
reply.hdrhead[POLL] = request.hdrhead[POLL];
reply.receive_ts = recvtime;
reply.origin_ts = request.transmit_ts;
uint64_t* rts = &reply.reference_ts;
uint64_t* tts = &reply.transmit_ts;
timestamp_64(rts);
timestamp_64(tts);
if (sendto(clisock, (char*) &reply, NTP_MSGLEN, 0, (struct sockaddr*) saddr_p, saddrlen) < NTP_MSGLEN)
{
_log_("Cannot send NTP reply", stdout);
}
else
{
_log_("Request reply sent back to client", stdout);
}
}
Global defines below:
/* Global NTP parameters */
#define NTP_UTC 2208988800U
#define NTP_PORT 123
#define NTP_MSGLEN 48
/* NTP header's first 4 bytes */
#define LIVN 0
#define STRATUM 1
#define POLL 2
#define PREC 3
/* NTP header field values */
#define NTP_MODE_SERVER 4
#define NTP_STRATUM_PRIMARY_SERVER 1
#define NTP_HOST_PRECISION -6
#define NTP_NODELAY 0
#define NTP_NODISPERSION 0
#define NTP_REF_ID_GPS *(uint32_t*)("GPS")
Where am I wrong?

Related

Get IP version from packet data

The pcap callback function returns the IP header and data as follows:
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data);
My understanding is the first 4 bits of the pkt_data is the IP version from which I can determine it is is IPv4 or IPv6. However, I've tried a few different ways to read the first 4 bits and I'm getting data that does not make sense.
For example, I defined the following structure:
struct ipdata {
u_char version : 4;
u_char dontcare : 4;
};
And then I tried to get the ip version using this code:
ipdata* pipdata;
pipdata = (ipdata*) pkt_data;
ip_ver = pipdata->version;
printf(" %d ", ip_ver);
The above method prints values of 3, 6, 9, 8 and 12. If I watch the traffic at the same time in Wireshark I see that most of the packets are IPv6.
Could someone who has done this clarify how would I go about reading the IP version?
Figure out the answer. Npcap returns the entire ethernet packet, so the first 14 bytes are the Ethernet header:
/* Length of the Ethernet Header (Data Link Layer) */
#define ETHERNET_HEADER_LEN 14
/* Ethernet addresses are 6 bytes */
#define ETHER_ADDR_LEN 6
/* Ethernet header */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address (i.e. Destination MAC Address) */
u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address (i.e. Source MAC Address) */
u_short ether_type; /* IP? ARP? RARP? etc */
};
You can figure out whether it is an IPv4 or IPv6 packet by looking at the ether_type in the above structure rather than the version in the IP header, such as:
/* Common ethernet types in Hex*/
#define ETHERNET_TYPE_IPv4 0x0800
#define ETHERNET_TYPE_IPv6 0x86DD
u_short eth_type;
ethernet = (struct sniff_ethernet*)(pkt_data);
eth_type = ntohs(ethernet->ether_type);
if (eth_type == ETHERNET_TYPE_IPv4) {
ipv4_handler(pkt_data);
}
else if (eth_type == ETHERNET_TYPE_IPv6)
{
ipv6_handler(pkt_data);
}
The IP header starts right after the ethernet header, so you can get it with code such as the following example for an IPv6 packet:
/* IPv6 header */
typedef struct ipv6_header
{
unsigned int
version : 4,
traffic_class : 8,
flow_label : 20;
uint16_t length;
uint8_t next_header;
uint8_t hop_limit;
struct in6_addr saddr;
struct in6_addr daddr;
} ipv6_header;
const ipv6_header* iph;
iph = (ipv6_header*)(pkt_data + ETHERNET_HEADER_LEN);
From there you can access the version and other information about the IP header. See this post for more information: Getting Npcap IPv6 source and destination addresses

Add TCP Option to ACK packet during 3-way handshake

I'm writing kernel module using netfilter. I just want to handle ACK for SYN/ACK (TCP three-way handshake). I use skb_is_tcp_pure_ack function, but ACK for data is also processed.
How can I do? My kernel version is 3.10.0-514.16.1.el7.x86_64.
Current code looks like this:
struct iphdr *iph;
struct tcphdr *tcph;
struct net *net;
unsigned int hdr_len;
unsigned int tcphoff;
if (!skb_is_tcp_pure_ack(skb)) {
return NF_ACCEPT;
}
/* add tcp option */
/* A netfilter instance to use */
static struct nf_hook_ops nfho __read_mostly = {
.hook = ato_hookfn,
.pf = PF_INET,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_LAST,
.owner = THIS_MODULE,
};
From the TCP state machine, you want to only match ACK packets when the state is TCP_SYN_SENT. Try adding another condition to check that, something like:
if (skb_is_tcp_pure_ack(skb)
&& skb->sk->sk_state == TCP_SYN_SENT) {
/*
* ACK(-only) packet during three-way handshake
*/
}
Also, note that skb_is_tcp_pure_ack was introduced in kernel version 4.* and not below.

Linux IPv6 source address selection when interface has >1 IPv6 addresses: How to deprecate one?

In the context of IPv6 source address selection for outgoing traffic in Linux:
I have some IPv6 address(es) on the interface.
I want the kernel to pick one of those as the source IPv6 addr.
I don't want the kernel to pick this address I'm about to send it as the source address for outgoing packets.
More concretely, in this snippet I would like for the kernel to select any other IPv6 address already on this interface when dontUseAsSourceAddressForOutgoingPkts is true.
What flags will yield that effect?
If I'm using the wrong ifaddrmsg struct for IPv6 addressing, which one should I be using?
Snippet containing further context:
int
NetLnkSock::IpAdd(const std::string &ifname,
const IpAddr &ipaddr,
int prefixlen,
bool dontUseAsSourceAddressForOutgoingPkts)
ifreq ifr;
nlmsghdr *nlh;
ifaddrmsg *ifa;
nlmsgerr *nlerr;
static uint32_t msg_seq = 0;
NlSock nlsock;
LogDev::Ostream logostr;
nlsock.bind();
memset(&ifr, 0, sizeof(ifr));
if (ifname.size() > IFNAMSIZ)
throw NetLnkNameErr();
copy(ifname.begin(), ifname.end(), ifr.ifr_name);
ifr.ifr_name[ifname.end() - ifname.begin()] = '\0';
nlh = (nlmsghdr *)rcvbuf;
nlh->nlmsg_len = sizeof(nlmsghdr);
nlh->nlmsg_type = RTM_NEWADDR;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = ++msg_seq;
nlh->nlmsg_pid = 0;
ifa = (ifaddrmsg *)&nlh[1];
ifa->ifa_family = (ipaddr.is_v4()) ? AF_INET : AF_INET6;
ifa->ifa_prefixlen = prefixlen;
/*
* My question is about the behavior of the kernel
* vis a vis source address selection for outgoing traffic
* where there are multiple IP's on this interface.
* How do the flags below impact the kernel's choice
* for source address selection?
*/
ifa->ifa_flags =
(dontUseAsSourceAddressForOutgoingPkts && ipaddr.is_v6()) ?
(IFA_F_SECONDARY | IFA_F_DEPRECATED) : 0;
/*
* I would like for the kernel to select any other IPv6
* address already on this interface when
* dontUseAsSourceAddressForOutgoingPkts is true.
* Will these flags yield that effect?
*/
ifa->ifa_scope = RT_SCOPE_UNIVERSE;
ifa->ifa_index = ifr.ifr_ifindex;
nlh->nlmsg_len += sizeof(ifaddrmsg);
if (ipaddr.is_v4()) {
IpAddr ip4_bcast;
char *buf = rcvbuf + nlh->nlmsg_len;
ip4_bcast.create_netmask(prefixlen, ipaddr);
ip4_bcast.from_v4(~ip4_bcast.get_v4() | ipaddr.get_v4());
nlh->nlmsg_len += NLMSG_ALIGN(setRtAttr(buf, IFA_LOCAL,
&ipaddr.get_v4(), sizeof(in_addr_t)));
/*
* Always send the netmask and broadcast even on delete.
* Linux seems to ignore the prefixlen set in the original
* message and simply matches by ip address on deletes.
*/
buf = rcvbuf + nlh->nlmsg_len;
nlh->nlmsg_len += NLMSG_ALIGN(setRtAttr(buf, IFA_ADDRESS,
&ipaddr.get_v4(), sizeof(in_addr_t)));
buf = rcvbuf + nlh->nlmsg_len;
nlh->nlmsg_len += NLMSG_ALIGN(setRtAttr(buf, IFA_BROADCAST,
&ip4_bcast.get_v4(), sizeof(in_addr_t)));
} else { /* AF_INET6 */
char *buf = rcvbuf + nlh->nlmsg_len;
buf = rcvbuf + nlh->nlmsg_len;
if (ipaddr.domain() != RD_DEFAULT_ID) { // Hal doesn't support route domains
throw NetLnkIpAddrErr();
}
nlh->nlmsg_len += NLMSG_ALIGN(setRtAttr(buf, IFA_LOCAL,
&ipaddr.get_v6(), sizeof(in6_addr)));
buf = rcvbuf + nlh->nlmsg_len;
nlh->nlmsg_len += NLMSG_ALIGN(setRtAttr(buf, IFA_ADDRESS,
&ipaddr.get_v6(), sizeof(in6_addr)));
}
nlsock.sendNlReq(rcvbuf);
}
RFC 3484 states:
Source Address Selection
<...>
Rule 3: Avoid deprecated addresses.
The addresses SA and SB have the same scope. If one of the two
source addresses is "preferred" and one of them is "deprecated" (in
the RFC 2462 sense), then prefer the one that is "preferred."
<...>
The rtnetlink(7) man pages briefly mention a struct called ifa_cacheinfo.
This struct contains two flags of notable import: ifa_valid and ifa_prefered.
In order to mark an IPv6 address as Deprecated, set its prefered_lft to zero. Additionally, it seems customary to also set valid_lft to 0xffffffff (forever) to emphasize the explicitly deprecated nature of this IPv6 address.
/*
* You have just put a new IPv6 address on the kernel with
* net link. You don't want it chosen as the source address
* of packets leaving this interface if there's at least one
* other IPv6 address already on this interface.
*
* Mark this IPv6 address as Deprecated on this interface,
* Causing LINUX not to choose it for source address of
* packets outgoing from this interface when there exists
* another, non-deprecated IPv6 address on this interface
*/
struct ifa_cacheinfo ci;
// This address is valid forever
ci.ifa_valid = 0xffffffff;
// A prefered ttl of 0 immediately deprecates this IPv6
ci.ifa_preferred = 0;
// <Send this cacheinfo to the kernel using net link>
The rtnetlink(7) man pages just say:
ifa_flags is a flag word of IFA_F_SECONDARY for secondary address (old alias interface), IFA_F_PERMANENT for a permanent address set by the user and other undocumented flags.
Indeed, the kernel sources do not seem to document them:
/* ifa_flags */
#define IFA_F_SECONDARY 0x01
#define IFA_F_TEMPORARY IFA_F_SECONDARY
#define IFA_F_NODAD 0x02
#define IFA_F_OPTIMISTIC 0x04
#define IFA_F_DADFAILED 0x08
#define IFA_F_HOMEADDRESS 0x10
#define IFA_F_DEPRECATED 0x20
#define IFA_F_TENTATIVE 0x40
#define IFA_F_PERMANENT 0x80
#define IFA_F_MANAGETEMPADDR 0x100
#define IFA_F_NOPREFIXROUTE 0x200
#define IFA_F_MCAUTOJOIN 0x400
#define IFA_F_STABLE_PRIVACY 0x800
However, the RFC 3549 "Linux Netlink as an IP Services Protocol" clarifies a bit more:
Flags: 8 bits
IFA_F_SECONDARY For secondary address (alias interface).
IFA_F_PERMANENT For a permanent address set by the user.
When this is not set, it means the address
was dynamically created (e.g., by stateless
autoconfiguration).
IFA_F_DEPRECATED Defines deprecated (IPV4) address.
IFA_F_TENTATIVE Defines tentative (IPV4) address (duplicate
address detection is still in progress).
So it seems the two flags are not related: one marks an interface address as being secondary (temporary); while the other defines a IPv4 address ("deprecated").
If you need to see exactly what are the implications of each flag, you can take a look at references for the symbol in the source code, for instance at IFA_F_SECONDARY and IFA_F_DEPRECATED.

Struct copied into byte array .. wrong alignement?

I'm trying to send some manually crafted ARP packets over the network,more specifically an ARP request to get the MAC address of a host.
I can't get the final packet right, on wireshark it stills shows some inconsistency.
Let me walk you through :
Here are the struct & typedef I use all over the program ,
I've defined
a IP struct ( => in_addr )
a MAC struct ( => ether_addr )
a Host struct composed of a MAC & IP
Custom struct to represent a Ethernet frame & an ARP frame.
The code:
#define ETH_ADDR_SIZE 6
#define IP_ADDR_SIZE 4
typedef u_char Packet;
typedef struct in_addr IP;
typedef struct ether_addr MAC;
struct Host {
IP ip;
MAC mac;
};
typedef struct pkt_eth {
MAC dest;
MAC src;
u_short type;
} pkt_eth;
typedef struct pkt_arp {
u_short htype;/* hardware type => ethernet , etc */
u_short ptype; /*protocol type => ipv4 or ipv6 */
u_char hard_addr_len; /* usually 6 bytes for ethernet */
u_char proto_addr_len; /*usually 8 bytes for ipv4 */
u_short opcode; /* type of arp */
MAC hard_addr_send;
IP proto_addr_send;
MAC hard_addr_dest;
IP proto_addr_dest;
} pkt_arp;
/* Designate our own MAC / IP addresses of the interface */
extern MAC mac;
extern IP ip;
extern char * interface;
/* Just some vars used to compare with the struct we use */
const MAC broadcast_mac = { 0xff,0xff,0xff,0xff,0xff,0xff };
const MAC null_mac = { 0x00,0x00,0x00,0x00,0x00,0x00 };
const IP broadcast_ip = { 0xffffffff };
const IP null_ip = { 0x00000000 };
const struct Host null_host = {{ 0x00000000 },
{ 0x00,0x00,0x00,0x00,0x00,0x00 }};
/* Empty mac address which can be used as a temp variable */
MAC tmp_mac = { 0x00,0x00,0x00,0x00,0x00,0x00 };
IP tmp_ip = { 0x00000000 };
Here is the relevant function :
int
arp_resolve_mac ( struct Host * host )
{
struct pkt_arp * arp;
struct pkt_eth * eth;
/*Create the request packet */
Packet * request = arp_packet(REQUEST);
eth = (struct pkt_eth *) (request);
arp = (struct pkt_arp *) (request + ETH_SIZE);
/* ethernet frame */
copy_mac(&eth->dest,&broadcast_mac);
copy_mac(&eth->src,&mac);
/* arp request => mac dest address set to null */
copy_mac(&arp->hard_addr_send,&mac);
copy_mac(&arp->hard_addr_dest,&null_mac);
/* arp request => target ip ! */
copy_ip(&arp->proto_addr_send,&ip);
copy_ip(&arp->proto_addr_dest,&host->ip);
/* Set up sniffing. Better to do it before so less
* prepare time and if any error occurs, no need to send
* the packet. less intrusive */
pcap_init(interface,"arp");
pcap_set_arp_analyzer(arp_analyzer_resolv);
/* Sets the tmp ip variable so we will know if it the right
* response we get or a response coming from another source */
tmp_ip = host->ip;
/* sends the packet */
if(pcap_send_packet(request,ARP_PACKET_SIZE) == -1) {
fprintf(stderr,"Error while sending ARP request packet.\n");
return -1;
}
....
}
Packet *
arp_packet ( int opcode )
{
struct pkt_arp * arp;
struct pkt_eth * eth;
Packet * bytes = (Packet *) malloc(ARP_PACKET_SIZE);
if(bytes == NULL) {
fprintf(stderr,"Could not alloc ARP packet.\n");
return NULL;
}
eth = (struct pkt_eth *) (bytes);
eth->type = htons(ETHERTYPE_ARP);
/* length about hard / proto ... */
arp = (struct pkt_arp *) (bytes + ETH_SIZE);
arp->htype = htons(1);
arp->ptype = htons(0x0800);
arp->hard_addr_len = ETH_ADDR_SIZE;
arp->proto_addr_len = IP_ADDR_SIZE;
/* reply or request */
arp->opcode = opcode == REQUEST ? htons(ARPOP_REQUEST) : htons(ARPOP_REPLY);
return bytes;
} /* ----- end of function arp_empty ----- */
void copy_mac(MAC * m1,const MAC * m2) {
memcpy(m1,m2,ETH_ADDR_SIZE);
}
void copy_ip(IP * i1,const IP * i2) {
memcpy(i1,i2,IP_ADDR_SIZE);
}
void copy_host(struct Host * h1,const struct Host * h2) {
copy_mac(&h1->mac,&h2->mac);
copy_ip(&h1->ip,&h2->ip);
}
Problem:
The created packet is not quite right. Everything is fine up to the hard_addr_send. After this field, there is 2 bytes 0x00,0x00, (seen in GDB) and then the IP address. But due to this offset, it's impossible to correctly parse this packet. For example,in wireshark, instead of getting "10.0.0.1", I've got "0.0.10.0" for IP.
Here is the transcript of GDB :
/** 14 to pass ethernet frame & 4 + 2 + 2 to go to the addresses section*/
(gdb) x/6xb request+14+4+2+2
/** My MAC address , field hard_addr_send. it's GOOD. */
0x606b16: 0x34 0x67 0x20 0x01 0x9a 0x67
(gdb) x/6xb request+14+4+2+2+6
/** 6bytes later, supposedly my IP address.
* It should be 10.0.0.7 but you can see the 0x0a shifted by 2 bytes */
0x606b1c: 0x00 0x00 0x0a 0x00 0x00 0x07
In the method "arp_resolv_mac", everything info is right, i.e. struct Host contains the good information etc; I've checked everything.
I just don't get this offset by 2 bytes ... In a older versions, not using all theses new structs (only char *), I've already succeed at creating a right ARP packet, so I'm kind of wondering if this is not due to the struct, but my knowledge of C does not extend to the memory alignement subject ...!
Thank you.
The problem is that your structs are not packed. One solution would be to use packed structs, i.e.
typedef struct __attribute__ ((__packed__)) pkt_arp {
u_short htype;/* hardware type => ethernet , etc */
u_short ptype; /*protocol type => ipv4 or ipv6 */
u_char hard_addr_len; /* usually 6 bytes for ethernet */
u_char proto_addr_len; /*usually 8 bytes for ipv4 */
u_short opcode; /* type of arp */
MAC hard_addr_send;
IP proto_addr_send;
MAC hard_addr_dest;
IP proto_addr_dest;
} pkt_arp;
However, that is a gcc-specific extension other compilers may not support.
In my opinion, the best solution is accessing the elements of the byte array directly instead of using structs. Yes, it adds a few lines of code, but it's guaranteed to work for compilers that don't implement packed structs too.

Pointer to ICMPHeader

I am reading packets in pcap format, and have determined that some have the protocol ICMP. I think that if that's the case, the ICMP header immediately follows the IP header. However, I get the wrong ICMP type (echo reply, request) for each of my results. I.e, here is the correct output for one such packet which has an echo reply:
Packet number: 2 Packet Len: 74
Ethernet Header
Dest MAC: 0:2:2d:90:75:89
Source MAC: 0:6:25:78:c4:7d
Type: IP
IP Header
TOS: 0xff
TTL: 52
Protocol: ICMP
Checksum: Incorrect (0x5565)
Sender IP: 66.94.230.35
Dest IP: 192.168.1.102
ICMP Header
Type: Reply
I know that echo reply has a code of 8, but instead of that I get a 0, indicating "request", and this happens for all packets when I try to get the type. I feel that I may be pointing my ICMP header to the wrong location following the IP header. My IP information though is correct. Here is how I am adjusting the pointers to the IP and ICMP headers:
EtherHeader *eth = (EtherHeader *)packet;
IPHeader *iph;
TCPHeader *tcp;
ICMPHeader *icm;
---
ipLen = ntohs(iph->totLen * 4);
if(iph->protocol == ICMP) {
icm = (ICMPHeader *)(packet + ETHER_SIZE + (ntohs(iph->totLen)));
printf("%d\n", icm->type);;
}
Is there anything wrong with how I am setting my pointer to the ICMP header?
Here are my headers for IP and ICMP:
typedef struct __attribute__((__packed__)) IPHeader {
#if __BYTE_ORDER__ == __LITTLE_ENDIAN__
uint8_t hdrLen:4;
uint8_t version:4;
#else
uint8_t version:4;
uint8_t hdrLen:4;
#endif
uint8_t TOS;
uint16_t totLen;
uint16_t id;
uint16_t offset;
#define DF 0x4
#define MF 0x2
#define OFF 0
uint8_t TTL;
uint8_t protocol;
uint16_t checksum;
struct in_addr srcIP;
struct in_addr destIP;
}IPHeader;
typedef struct __attribute__((__packed__)) ICMPHeader {
uint8_t type;
}ICMPHeader;
Your understanding of the control message type numbers is backwards:
I know that echo reply has a code of 8, but instead of that I get a 0, indicating "request",
The correct values are:
0 Echo Reply
....
8 Echo Request
Internet Control Message Protocol - Control Messages
Also, although this doesn't appear to be used, you have an order of operations problem here - you can't do anything with the value until you change its byte order:
ntohs(iph->totLen * 4);
needs to be
ntohs(iph->totLen) * 4;
I know that echo reply has a code of 8, but instead of that I get a 0,
No it doesn't. ICMP echo reply has a type of 0. type 8 is Echo (request).
See e.g. this

Resources