diff --git a/.vscode/settings.json b/.vscode/settings.json index 257d7ee..b0434c4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "clangd.path": "/usr/bin/clangd" + "clangd.path": "/usr/bin/clangd", + "clangd.arguments": [ "--header-insertion=never" ] } \ No newline at end of file diff --git a/README.md b/README.md index defda00..f534829 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # cws -A Web Server written in C (educational purposes) +A simple Web Server written in C (learning purposes) ## Requirements - [meson](https://mesonbuild.com/index.html) @@ -26,3 +26,5 @@ And then run `cws`! ## Resources - [Beej's Guide to Network Programming](https://beej.us/guide/bgnet/) + +You can find my journey inside the `notes` directory! diff --git a/include/colors.h b/include/colors.h new file mode 100644 index 0000000..cc5e36f --- /dev/null +++ b/include/colors.h @@ -0,0 +1,11 @@ +#ifndef __COLORS_H__ +#define __COLORS_H__ + +#define RED "\033[31m" +#define GREEN "\033[32m" +#define YELLOW "\033[33m" +#define BLUE "\033[34m" +#define BOLD "\033[1m" +#define RESET "\033[0m" + +#endif \ No newline at end of file diff --git a/include/main.h b/include/main.h index 581f0ac..91284c3 100644 --- a/include/main.h +++ b/include/main.h @@ -1,7 +1,6 @@ #ifndef __MAIN_H__ #define __MAIN_H__ -#define GREEN_COLOR "\033[32m" -#define RESET_COLOR "\033[0m" +#include #endif \ No newline at end of file diff --git a/include/server.h b/include/server.h index 14c3cea..f3caba0 100644 --- a/include/server.h +++ b/include/server.h @@ -1,13 +1,20 @@ #ifndef __SERVER_H__ #define __SERVER_H__ -#include -#include -#include +#include #include +#include +#include +#include + +#include +#include +#include #define PORT 3030 // how many pending connections the queue will hold #define BACKLOG 10 +int start_server(void); + #endif diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..7f87d4f --- /dev/null +++ b/include/utils.h @@ -0,0 +1,17 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include + +// Print every IPs of a hostname +void print_ips(const char *hostname, const char *port); + +#endif diff --git a/notes/basic-concepts.md b/notes/basic-concepts.md new file mode 100644 index 0000000..488f40a --- /dev/null +++ b/notes/basic-concepts.md @@ -0,0 +1,118 @@ +# Basic concepts +Before reading, this document could contain errors, please check everything you read. + +- [Basic concepts](#basic-concepts) + - [Socket](#socket) + - [Internet sockets](#internet-sockets) + - [Byte Order](#byte-order) + - [Structs](#structs) + - [struct addrinfo](#struct-addrinfo) + - [struct sockaddr](#struct-sockaddr) + - [struct sockaddr\_in](#struct-sockaddr_in) + - [struct sockaddr\_storage](#struct-sockaddr_storage) + - [IP Addresses](#ip-addresses) + - [getaddrinfo()](#getaddrinfo) + + +### Socket +When Unix programs do some I/O they do it reading/writing to a **file descriptor**. A file descriptor is an integer associated with an open file, it can be anything. To communicate over the internet using a file descriptor we'll make a call to the `socket()` system routine. + +### Internet sockets +There are two types of Internet sockets: +1. Stream Sockets (SOCK_STREAM) - error-free and a realiable two-way communication (TCP) +2. Datagram Sockets (SOCK_DGRAM) - connectionless (UDP) + +### Byte Order +- Big-Endian (also called **Network Byte Order**) +- Little-Endian + +Before making any transmission we have to convert the byte order to a Network Byte Order, we can do this with simple functions: + +| Function | Description | +| -------- | --------------------- | +| htons() | Host to Network Short | +| htonl() | Host to Network Long | +| ntohs() | Network to Host Short | +| ntohl() | Network to Host Long | + +And convert the answer to the host byte order. + +### Structs +#### struct addrinfo +This struct prepares the socket address strcutures for subsequent use. + +```c +struct addrinfo { + int ai_flags; // AI_PASSIVE, AI_CANONNAME, etc. + int ai_family; // AF_INET, AF_INET6, AF_UNSPEC + int ai_socktype; // SOCK_STREAM, SOCK_DGRAM + int ai_protocol; // use 0 for "any" + size_t ai_addrlen; // size of ai_addr in bytes + struct sockaddr *ai_addr; // struct sockaddr_in or _in6 + char *ai_canonname; // full canonical hostname + + struct addrinfo *ai_next; // linked list, next node +}; +``` + +#### struct sockaddr +Inside the struct we can see there is a pointer to the `struct sockaddr`, that is defined as follows: + +```c +struct sockaddr { + unsigned short sa_family; // address family, AF_xxx + char sa_data[14]; // 14 bytes of protocol address +}; +``` + +#### struct sockaddr_in +But, we can avoid to pack manually the stuff inside this struct and use the `struct sockaddr_in` (or `struct sockaddr_in6` for IPv6) with a fast cast that is made for the Internet: + +```c +struct sockaddr_in { + short int sin_family; // Address family, AF_INET + unsigned short int sin_port; // Port number + struct in_addr sin_addr; // Internet address + unsigned char sin_zero[8]; // Same size as struct sockaddr +}; +``` + +`sin_zero` is used to pad the struct to the length of a sockaddr and it should be set to all zeros (`memset()`). + +#### struct sockaddr_storage +This struct is designed to storage both IPv4 and IPv6 structures. Example, when a client is going to connect to your server you don't know if it is a IPv4 or IPv6 so you use this struct and then cast to what you need (check the `ss_family` first). + +```c +struct sockaddr_storage { + sa_family_t ss_family; // address family + + // all this is padding, implementation specific, ignore it: + char __ss_pad1[_SS_PAD1SIZE]; + int64_t __ss_align; + char __ss_pad2[_SS_PAD2SIZE]; +}; +``` + +### IP Addresses +Here's a way to convert an IP address string into a struct. + +```c +struct sockaddr_in sa; +struct sockaddr_in6 sa6; + +inet_pton(AF_INET, "192.168.0.1", &(sa.sin_addr)); +inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr)); +``` + +There is a very easy function called `inet_pton()`, *pton* stands for **Presentation to network**. If you want to do the same but from binary to string you have `inet_ntop()`. + +### getaddrinfo() +```c +int getaddrinfo(const char *node, // e.g. "www.example.com" or IP + const char *service, // e.g. "http" or port number + const struct addrinfo *hints, + struct addrinfo **res); +``` + +The `node` could be a host name or IP address. `service` could be a port number or a service found in `/etc/services` (e.g. "http" or "ftp" or "telnet"). +`hints` points to a struct you already filled and `res` contains a linked list of results. \ No newline at end of file diff --git a/src/main.c b/src/main.c index 40b3562..8ec0676 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,17 @@ #include "main.h" -#include +#include "colors.h" +#include "server.h" +#include "utils.h" -int main(void) { - puts(GREEN_COLOR "Running cws..." RESET_COLOR); +int main(int argc, char **argv) { + fprintf(stdout, BOLD GREEN "Running cws...\n" RESET); + + int ret = start_server(); + if (ret < 0) { + fprintf(stderr, BOLD RED "Unable to start web server\n"); + } + + print_ips("google.com", "80"); + + return 0; } diff --git a/src/meson.build b/src/meson.build index 9fa26ab..77cabbc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1 +1 @@ -sources = files('main.c', 'server.c') \ No newline at end of file +sources = files('main.c', 'server.c', 'utils.c') \ No newline at end of file diff --git a/src/server.c b/src/server.c index aef1b66..6291f33 100644 --- a/src/server.c +++ b/src/server.c @@ -1,5 +1,34 @@ #include "server.h" +#include "colors.h" int start_server(void) { + char ipv4[INET_ADDRSTRLEN]; + struct sockaddr_in sa; + + inet_pton(AF_INET, "192.168.0.1", &(sa.sin_addr)); + inet_ntop(AF_INET, &(sa.sin_addr), ipv4, INET_ADDRSTRLEN); + + fprintf(stdout, BLUE "IPv4: %s\n" RESET, ipv4); + return 0; } + +void get_local_ip(void) { + char service[] = "3030"; + struct addrinfo hints; + struct addrinfo *res; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; // IPv4 or IPv6 + hints.ai_socktype = SOCK_STREAM; // TCP + hints.ai_flags = AI_PASSIVE; // fill in IP for me + + int status = getaddrinfo(NULL, service, &hints, &res); + if (status != 0) { + fprintf(stderr, RED "getaddrinfo() error: %s\n" RESET, + gai_strerror(status)); + exit(1); + } + + freeaddrinfo(res); +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..937cd0b --- /dev/null +++ b/src/utils.c @@ -0,0 +1,40 @@ +#include "utils.h" +#include "colors.h" + +void print_ips(const char *hostname, const char *port) { + struct addrinfo ai; + struct addrinfo *res; + + memset(&ai, 0, sizeof ai); + + ai.ai_family = AF_UNSPEC; + ai.ai_socktype = SOCK_STREAM; + + int status = getaddrinfo(hostname, port, &ai, &res); + if (status < 0) { + fprintf(stderr, RED "getaddrinfo(): %s\n" RESET, + gai_strerror(status)); + exit(1); + } + + char ipv4[INET_ADDRSTRLEN]; + char ipv6[INET6_ADDRSTRLEN]; + + for (struct addrinfo *p = res; p != NULL; p = p->ai_next) { + if (p->ai_family == AF_INET) { + struct sockaddr_in *sin = + (struct sockaddr_in *)p->ai_addr; + inet_ntop(AF_INET, &sin->sin_addr, ipv4, + INET_ADDRSTRLEN); + fprintf(stdout, BLUE "%s\n" RESET, ipv4); + } else if (p->ai_family == AF_INET6) { + struct sockaddr_in6 *sin6 = + (struct sockaddr_in6 *)p->ai_addr; + inet_ntop(AF_INET6, &sin6->sin6_addr, ipv6, + INET6_ADDRSTRLEN); + fprintf(stdout, BLUE "%s\n" RESET, ipv6); + } + } + + freeaddrinfo(res); +} \ No newline at end of file