diff options
Diffstat (limited to 'src/client')
-rw-r--r-- | src/client/addr.c | 254 | ||||
-rw-r--r-- | src/client/addr.h | 69 | ||||
-rw-r--r-- | src/client/binding.c | 167 | ||||
-rw-r--r-- | src/client/binding.h | 26 | ||||
-rw-r--r-- | src/client/client.c | 177 | ||||
-rw-r--r-- | src/client/client.h | 15 |
6 files changed, 708 insertions, 0 deletions
diff --git a/src/client/addr.c b/src/client/addr.c new file mode 100644 index 0000000..82f0e2e --- /dev/null +++ b/src/client/addr.c @@ -0,0 +1,254 @@ +#include <errno.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <netdb.h> +#include <poll.h> + +#include "addr.h" + +void create_ip_addr(uint8_t* domain, IpAddr* addr) { + addr->type = V4; + memcpy(&addr->data.v4, domain, 4); +} + +void create_ip_addr6(uint8_t* domain, IpAddr* addr) { + addr->type = V6; + memcpy(&addr->data.v6, domain, 16); +} + +void ip_addr_any(IpAddr* addr) { + addr->type = V4; + addr->data.v4.s_addr = htonl(INADDR_ANY); +} + +void ip_addr_any6(IpAddr* addr) { + addr->type = V6; + addr->data.v6 = in6addr_any; +} + +bool ip_addr_name(const char* hostname, IpAddr* addr) { + + struct hostent *he; + struct in_addr **addr_list; + int i; + + if ((he = gethostbyname(hostname)) == NULL) { + return false; + } + + addr_list = (struct in_addr **) he->h_addr_list; + + for(i = 0; addr_list[i] != NULL; i++) { + addr->data.v4 = *addr_list[i]; + addr->type = V4; + return true; + } + + return false; +} + +static struct sockaddr_in create_socket_addr_v4(IpAddr addr, uint16_t port) { + struct sockaddr_in socketaddr; + memset(&socketaddr, 0, sizeof(socketaddr)); + socketaddr.sin_family = AF_INET; + socketaddr.sin_port = htons(port); + socketaddr.sin_addr = addr.data.v4; + return socketaddr; +} + +static struct sockaddr_in6 create_socket_addr_v6(IpAddr addr, uint16_t port) { + struct sockaddr_in6 socketaddr; + memset(&socketaddr, 0, sizeof(socketaddr)); + socketaddr.sin6_family = AF_INET6; + socketaddr.sin6_port = htons(port); + socketaddr.sin6_addr = addr.data.v6; + return socketaddr; +} + +static size_t get_addr_len(AddrType type) { + if (type == V4) { + return sizeof(struct sockaddr_in); + } else if (type == V6) { + return sizeof(struct sockaddr_in6); + } else { + return 0; + } +} + +void create_socket_addr(uint16_t port, IpAddr addr, SocketAddr* socket) { + socket->type = addr.type; + if (addr.type == V4) { + socket->data.v4 = create_socket_addr_v4(addr, port); + } else if(addr.type == V6) { + socket->data.v6 = create_socket_addr_v6(addr, port); + } else { + exit(EXIT_FAILURE); + } + socket->len = get_addr_len(addr.type); +} + +#define ADDR_DOMAIN(addr, var) \ + struct sockaddr* var; \ + if (addr->type == V4) { \ + var = (struct sockaddr*) &addr->data.v4; \ + } else if (addr->type == V6) { \ + var = (struct sockaddr*) &addr->data.v6; \ + } else { \ + return -1; \ + } + +#define ADDR_AFNET(type, var) \ + int var; \ + if (type == V4) { \ + var = AF_INET; \ + } else if (type == V6) { \ + var = AF_INET6; \ + } else { \ + return -1; \ + } + +int32_t create_udp_socket(AddrType type, UdpSocket* sock) { + ADDR_AFNET(type, __domain) + sock->type = type; + sock->sockfd = socket(__domain, SOCK_DGRAM, 0); + return sock->sockfd; +} + +int32_t bind_udp_socket(SocketAddr* addr, UdpSocket* sock) { + if (addr->type == V6) { + int v6OnlyEnabled = 0; + int32_t res = setsockopt( + sock->sockfd, + IPPROTO_IPV6, + IPV6_V6ONLY, + &v6OnlyEnabled, + sizeof(v6OnlyEnabled) + ); + if (res < 0) return res; + } + ADDR_DOMAIN(addr, __addr) + return bind(sock->sockfd, __addr, addr->len); +} + +static bool timeout (int connfd, int ms) { + struct pollfd fd; + int res; + + fd.fd = connfd; + fd.events = POLLIN; + res = poll(&fd, 1, ms); + return res == 0; +} + +int32_t read_udp_socket(UdpSocket* socket, void* buffer, uint16_t len, SocketAddr* clientaddr) { + clientaddr->type = socket->type; + clientaddr->len = get_addr_len(socket->type); + ADDR_DOMAIN(clientaddr, __addr) + if (timeout(socket->sockfd, 1000)) { + errno = ETIMEDOUT; + return -1; + } + return recvfrom( + socket->sockfd, + buffer, + (size_t) len, + MSG_WAITALL, + __addr, + (uint32_t*) &clientaddr->len + ); +} + +int32_t write_udp_socket(UdpSocket* socket, void* buffer, uint16_t len, SocketAddr* clientaddr) { + ADDR_DOMAIN(clientaddr, __addr) + return sendto( + socket->sockfd, + buffer, + (size_t) len, + MSG_CONFIRM, + __addr, + (uint32_t) clientaddr->len + ); +} + +int32_t close_udp_socket(UdpSocket* socket) { + return close(socket->sockfd); +} + +int32_t create_tcp_socket(AddrType type, TcpSocket* sock) { + ADDR_AFNET(type, __domain) + sock->type = type; + sock->sockfd = socket(__domain, SOCK_STREAM, 0); + return sock->sockfd; +} + +int32_t bind_tcp_socket(SocketAddr* addr, TcpSocket* sock) { + if (addr->type == V6) { + int v6OnlyEnabled = 0; + int32_t res = setsockopt( + sock->sockfd, + IPPROTO_IPV6, + IPV6_V6ONLY, + &v6OnlyEnabled, + sizeof(v6OnlyEnabled) + ); + if (res < 0) return res; + } + ADDR_DOMAIN(addr, __addr) + return bind(sock->sockfd, __addr, addr->len); +} + +int32_t listen_tcp_socket(TcpSocket* socket, uint32_t max) { + return listen(socket->sockfd, max); +} + +int32_t accept_tcp_socket(TcpSocket* socket, TcpStream* stream) { + stream->clientaddr.type = socket->type; + memset(&stream->clientaddr, 0, sizeof(SocketAddr)); + SocketAddr* addr = &stream->clientaddr; + ADDR_DOMAIN(addr, __addr) + stream->streamfd = accept( + socket->sockfd, + __addr, + (uint32_t*) &stream->clientaddr.len + ); + return stream->streamfd; +} + +int32_t close_tcp_socket(TcpSocket* socket) { + return close(socket->sockfd); +} + +int32_t connect_tcp_stream(SocketAddr* servaddr, TcpStream* stream) { + TcpSocket socket; + int32_t res = create_tcp_socket(servaddr->type, &socket); + if (res < 0) return res; + stream->clientaddr = *servaddr; + stream->streamfd = socket.sockfd; + ADDR_DOMAIN(servaddr, __addr) + return connect( + socket.sockfd, + __addr, + servaddr->len + ); +} + +int32_t read_tcp_stream(TcpStream* stream, void* buffer, uint16_t len) { + if (timeout(stream->streamfd, 3000)) { + errno = ETIMEDOUT; + return -1; + } + return recv(stream->streamfd, buffer, len, MSG_WAITALL); +} + +int32_t write_tcp_stream(TcpStream* stream, void* buffer, uint16_t len) { + return send(stream->streamfd, buffer, len, MSG_NOSIGNAL); +} + +int32_t close_tcp_stream(TcpStream* stream) { + return close(stream->streamfd); +} diff --git a/src/client/addr.h b/src/client/addr.h new file mode 100644 index 0000000..28c6066 --- /dev/null +++ b/src/client/addr.h @@ -0,0 +1,69 @@ +#pragma once + +#include "../packet/record.h" + +#include <string.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/types.h> + +typedef enum { + V4, + V6 +} AddrType; + +typedef struct { + AddrType type; + union { + struct in_addr v4; + struct in6_addr v6; + } data; +} IpAddr; + +void create_ip_addr(uint8_t* domain, IpAddr* addr); +void create_ip_addr6(uint8_t* domain, IpAddr* addr); +void ip_addr_any(IpAddr* addr); +void ip_addr_any6(IpAddr* addr); +bool ip_addr_name(const char* name, IpAddr* addr); + +typedef struct { + AddrType type; + union { + struct sockaddr_in v4; + struct sockaddr_in6 v6; + } data; + size_t len; +} SocketAddr; + +void create_socket_addr(uint16_t port, IpAddr addr, SocketAddr* socket); + +typedef struct { + AddrType type; + uint32_t sockfd; +} UdpSocket; + +int32_t create_udp_socket(AddrType type, UdpSocket* socket); +int32_t bind_udp_socket(SocketAddr* addr, UdpSocket* socket); +int32_t read_udp_socket(UdpSocket* socket, void* buffer, uint16_t len, SocketAddr* clientaddr); +int32_t write_udp_socket(UdpSocket* socket, void* buffer, uint16_t len, SocketAddr* clientaddr); +int32_t close_udp_socket(UdpSocket* socket); + +typedef struct { + AddrType type; + uint32_t sockfd; +} TcpSocket; + +typedef struct { + SocketAddr clientaddr; + uint32_t streamfd; +} TcpStream; + +int32_t create_tcp_socket(AddrType type, TcpSocket* socket); +int32_t bind_tcp_socket(SocketAddr* addr, TcpSocket* socket); +int32_t listen_tcp_socket(TcpSocket* socket, uint32_t max); +int32_t accept_tcp_socket(TcpSocket* socket, TcpStream* stream); +int32_t close_tcp_socket(TcpSocket* socket); +int32_t connect_tcp_stream(SocketAddr* servaddr, TcpStream* stream); +int32_t read_tcp_stream(TcpStream* stream, void* buffer, uint16_t len); +int32_t write_tcp_stream(TcpStream* stream, void* buffer, uint16_t len); +int32_t close_tcp_stream(TcpStream* stream); diff --git a/src/client/binding.c b/src/client/binding.c new file mode 100644 index 0000000..9917a50 --- /dev/null +++ b/src/client/binding.c @@ -0,0 +1,167 @@ +#include <ctype.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "addr.h" +#include "binding.h" + +static void read_to_packet(uint8_t* buf, uint16_t len, Packet* packet) { + PacketBuffer* pkbuffer = buffer_create(len); + for (int i = 0; i < len; i++) { + buffer_write(pkbuffer, buf[i]); + } + buffer_seek(pkbuffer, 0); + read_packet(pkbuffer, packet); + buffer_free(pkbuffer); +} + +static bool read_udp(Connection* connection, Packet* packet) { + uint8_t buffer[512]; + int32_t n = read_udp_socket( + &connection->sock.udp.udp, + buffer, + 512, + &connection->sock.udp.clientaddr + ); + if (n < 0) { + return false; + } + read_to_packet(buffer, n, packet); + return true; +} + +static bool read_tcp(Connection* connection, Packet* packet) { + uint16_t len; + if ( read_tcp_stream( + &connection->sock.tcp, + &len, + sizeof(uint16_t) + ) < 2) { + return false; + } + len = ntohs(len); + + uint8_t buffer[len]; + if (read_tcp_stream( + &connection->sock.tcp, + buffer, + len + ) < len) { + return false; + } + + read_to_packet(buffer, len, packet); + return true; +} + +static bool read_connection(Connection* connection, Packet* packet) { + if (connection->type == UDP) { + return read_udp(connection, packet); + } else if (connection->type == TCP) { + return read_tcp(connection, packet); + } + return false; +} + +static bool write_udp(Connection* connection, uint8_t* buf, uint16_t len) { + if (len > 512) { + buf[2] = buf[2] | 0x03; + len = 512; + } + return write_udp_socket( + &connection->sock.udp.udp, + buf, + len, + &connection->sock.udp.clientaddr + ) == len; +} + +static bool write_tcp(Connection* connection, uint8_t* buf, uint16_t len) { + uint16_t net_len = htons(len); + if (write_tcp_stream( + &connection->sock.tcp, + &net_len, + sizeof(uint16_t) + ) < 0) { + return false; + } + + if (write_tcp_stream( + &connection->sock.tcp, + buf, + len + ) < 0) { + return false; + } + + return true; +} + +static bool write_connection(Connection* connection, Packet* packet) { + PacketBuffer* pkbuffer = buffer_create(64); + write_packet(pkbuffer, packet); + uint16_t len = buffer_get_size(pkbuffer); + uint8_t* buffer = buffer_get_ptr(pkbuffer); + bool success = false; + if(connection->type == UDP) { + success = write_udp(connection, buffer, len); + } else if(connection->type == TCP) { + success = write_tcp(connection, buffer, len); + }; + buffer_free(pkbuffer); + return success; +} + +void free_connection(Connection* connection) { + if (connection->type == TCP) { + close_tcp_stream(&connection->sock.tcp); + } +} + +static bool create_udp_request(SocketAddr* addr, Connection* request) { + if ( create_udp_socket(addr->type, &request->sock.udp.udp) < 0) { + return false; + } + request->sock.udp.clientaddr = *addr; + return true; +} + +static bool create_tcp_request(SocketAddr* addr, Connection* request) { + if( connect_tcp_stream(addr, &request->sock.tcp) < 0) { + return false; + } + return true; +} + +bool create_request(BindingType type, SocketAddr* addr, Connection* request) { + request->type = type; + if (type == UDP) { + return create_udp_request(addr, request); + } else if (type == TCP) { + return create_tcp_request(addr, request); + } else { + return true; + } +} + +bool request_packet(Connection* request, Packet* in, Packet* out) { + if (!write_connection(request, in)) { + return false; + } + if (!read_connection(request, out)) { + return false; + } + return true; +} + +void free_request(Connection* connection) { + if (connection->type == UDP) { + close_udp_socket(&connection->sock.udp.udp); + } else if (connection->type == TCP) { + close_tcp_stream(&connection->sock.tcp); + } +} diff --git a/src/client/binding.h b/src/client/binding.h new file mode 100644 index 0000000..ea02c98 --- /dev/null +++ b/src/client/binding.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../packet/packet.h" +#include "addr.h" + +#include <netinet/in.h> + +typedef enum { + UDP, + TCP +} BindingType; + +typedef struct { + BindingType type; + union { + struct { + UdpSocket udp; + SocketAddr clientaddr; + } udp; + TcpStream tcp; + } sock; +} Connection; + +bool create_request(BindingType type, SocketAddr* addr, Connection* request); +bool request_packet(Connection* request, Packet* in, Packet* out); +void free_request(Connection* connection); diff --git a/src/client/client.c b/src/client/client.c new file mode 100644 index 0000000..31ef589 --- /dev/null +++ b/src/client/client.c @@ -0,0 +1,177 @@ +#include "client.h" +#include "../packet/question.h" +#include "addr.h" +#include "binding.h" + +#include <resolv.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <sys/time.h> + +void resolve_default_server(IpAddr* addr) { + res_init(); + addr->data.v4 = _res.nsaddr_list[0].sin_addr; + addr->type = V4; +} + +static void push_question(Question** buf, Question q, uint8_t* amount, uint8_t* capacity) { + if (*amount >= *capacity) { + *capacity *= 2; + *buf = realloc(*buf, sizeof(Question) * *capacity); + } + (*buf)[*amount] = q; + (*amount)++; +} + +void resolve_questions(int argc, char** argv, uint8_t* len, Question** questions) { + assert(argc > 0); + + uint8_t amount = 0; + uint8_t capacity = 1; + Question* buf = malloc(sizeof(Question) * capacity); + + Question q; + RecordType t; + if (argc == 1 && str_to_qtype(argv[0], &t)) { + create_question("", NS, &q); + push_question(&buf, q, &amount, &capacity); + *questions = buf; + *len = 1; + return; + } + + for (int i = 0; i < argc; i++) { + if (str_to_qtype(argv[i], &t)) { + if (i + 1 == argc) break; + create_question(argv[i+1], t, &q); + i++; + } else if (i + 1 < argc && str_to_qtype(argv[i+1], &t)){ + create_question(argv[i], t, &q); + i++; + } else { + create_question(argv[i], A, &q); + } + push_question(&buf, q, &amount, &capacity); + } + + *questions = buf; + *len = amount; +} + +static void print_result(Packet* packet, const char* type, uint32_t ms) { + printf(">> Recieved response\n"); + printf("Id: %hu, Code: %s\n", + packet->header.id, + str_from_code(packet->header.rescode) + ); + printf("Questions: %hu, Answers: %hu, Authorities: %hu, Resources: %hu\n", + packet->header.questions, + packet->header.answers, + packet->header.authoritative_entries, + packet->header.resource_entries + ); + + if (packet->header.questions > 0) { + printf("\n>> Question Section\n"); + for (uint16_t i = 0; i < packet->header.questions; i++) { + print_question(&packet->questions[i]); + } + } + + if (packet->header.answers > 0) { + printf("\n>> Answer Section\n"); + for (uint16_t i = 0; i < packet->header.answers; i++) { + print_record(&packet->answers[i]); + } + } + + if (packet->header.authoritative_entries > 0) { + printf("\n>> Authority Section\n"); + for (uint16_t i = 0; i < packet->header.authoritative_entries; i++) { + print_record(&packet->authorities[i]); + } + } + + if (packet->header.resource_entries > 0) { + printf("\n>> Resource Section\n"); + for (uint16_t i = 0; i < packet->header.resource_entries; i++) { + print_record(&packet->resources[i]); + } + } + + printf("\n>> Query time: %ums (%s)\n", ms, type); +} + +void resolve(IpAddr server, Options options, Question* questions, uint8_t len) { + struct timeval stop, start; + gettimeofday(&start, NULL); + + SocketAddr addr; + create_socket_addr(options.port, server, &addr); + + Packet req; + memset(&req, 0, sizeof(Packet)); + srand(time(NULL)); + req.header.id = rand() % 65536; + req.header.questions = len; + req.header.recursion_desired = true; + req.questions = questions; + + Connection conn; + if (options.force_tcp) goto tcp; + + if (!create_request(UDP, &addr, &conn)) { + printf("error: failed to create udp request: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + Packet res; + if (!request_packet(&conn, &req, &res)) { + free_request(&conn); + free_packet(&req); + printf("error: failed to request udp packet: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + free_request(&conn); + + if (!res.header.truncated_message) { + gettimeofday(&stop, NULL); + uint32_t ms = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; + + print_result(&res, "UDP", ms / 1000); + free_packet(&req); + free_packet(&res); + return; + } + + free_packet(&res); + printf("Response truncated, retrying in TCP...\n"); + +tcp: + + if (!create_request(TCP, &addr, &conn)) { + printf("error: failed to create tcp request: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!request_packet(&conn, &req, &res)) { + free_request(&conn); + printf("error: failed to request tcp packet: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + gettimeofday(&stop, NULL); + uint32_t ms = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; + + print_result(&res, "TCP", ms / 1000); + + free_request(&conn); + free_packet(&req); + free_packet(&res); +} + diff --git a/src/client/client.h b/src/client/client.h new file mode 100644 index 0000000..7e0e391 --- /dev/null +++ b/src/client/client.h @@ -0,0 +1,15 @@ +#pragma once + +#include "addr.h" +#include "../packet/packet.h" + +typedef struct { + uint16_t port; + bool force_tcp; +} Options; + +void resolve_default_server(IpAddr* addr); + +void resolve_questions(int argc, char** argv, uint8_t* len, Question** questions); + +void resolve(IpAddr server, Options options, Question* questions, uint8_t lem); |