titzer / virgil

A fast and lightweight native programming language
1.2k stars 42 forks source link

[x86_64-linux]: Add basic networking support #117

Closed k-sareen closed 1 year ago

k-sareen commented 1 year ago

This commit adds basic networking support to the x86_64 Linux backend. This commit only implements client sockets.

k-sareen commented 1 year ago

Pretty much a WIP. Wanted to get your thoughts before I added more support. Note that I've just shoved everything in the Linux x86_64 backend but certain things like InetAddress should be general across backends.

Here's a sample program to test this:

def main() {
        def localhost = Net.LOCALHOST_V4;
        def sock = ClientSocket.new(localhost, 5555);
        def buffer = Array<byte>.new(512);
        System.puts("Hello ");
        System.puti(sock.fd);
        System.ln();
        def ret = sock.connect();
        if (ret == -1) {
                System.error("Error", "Socket connection failed!");
        }
        sock.write("Testing\n");
        System.puti(sock.read(buffer));
        System.ln();
        System.puts(buffer);
        sock.close();
}

In a separate terminal run ncat -e /bin/cat -k -l 5555 -v and you should see Testing print in the terminal running Virgil code.

The current code only implements a client socket -- I'll implement a server socket later.

k-sareen commented 1 year ago

Couple of things:

  1. I'm not happy with how InetAddress is implemented as there is no size for the arrays. Since InetAddress is a public type, theoretically anyone can create a faulty IP address. Ideally it's a u8[4] and u8[16] array for IPv4 and v6 respectively. I have a proposed solution in the comments, but I've just opted for this as it is currently easier to work with.
  2. I'll add parsing support for IPv4 and IPv6 addresses later. It might be better to directly use a host: string field for the constructor in SocketBase instead of the naked InetAddress. This depends on the API we want to provide to the user.
  3. Currently there is a fairly disgusting hack to marshal the data across the kernel. Is there a better way to do this other than structs which aren't stable yet, from my understanding.
  4. Also network-byte order is defined to be big-endian. x86 is little-endian, so I have to convert between the two. I think x86 is supposed to be little-endian only so current implementation should be fine -- I've still added an comment there saying that it should be converted to big-endian.
k-sareen commented 1 year ago

The x86-linux backend fails because the lib/util/*.v3 files are added to the -rt.files=. How am I supposed to add the DataWriter dependency? I guess once it's in lib/net/x86-64-linux, it should be fine.

k-sareen commented 1 year ago

Still not sure how to deal with repeated type definitions for x86_64-linux. Also, apologies, it seems like the x86-linux and x86-64-linux CI configs were swapped. See #118.

k-sareen commented 1 year ago

I've added the ServerSocket type now. The server.accept() function returns another socket for communication. Here's a sample program using this:

def main() {
        def any = Net.ANY_V4;
        def sock = ServerSocket.new(any, 5555);
        def buffer = Array<byte>.new(512);
        System.puts("Hello ");
        System.puti(sock.fd);
        System.ln();
        def ret = sock.bind();
        if (ret == -1) {
                System.error("Error", "Socket connection failed!");
        }
        sock.listen();
        while (true) {
                var connection = sock.accept();
                if (connection.fd == -1) {
                        System.error("Error", "Could not accept connections on socket!");
                }
                System.puts("Connection on fd ");
                System.puti(connection.fd);
                System.ln();
                connection.read(buffer);
                System.puts("recv msg: \"");
                System.puts(buffer);
                System.puts("\"\n");
                connection.write("Hello stranger!\n");
                connection.close();
        }
        sock.close();
}

You can use something like ncat 127.0.0.1 5555 -v or even just the ClientSocket sample program above (I've edited it to be up-to-date). I have to look into how to support a persistent socket connection so one can send multiple messages in the same session (currently that doesn't work as you will see if you run the above ncat command and try to send multiple messages).

k-sareen commented 1 year ago

By the way, is it possible to have private types or classes? I don't want to make the IntPointer type public, but can't figure out a way to make it private. Also, I personally find not being able to set a def var field from a subclass a bit non-intuitive. I'm getting around it with a setter-method, but I think it should be allowed generally without a setter-method, no?

k-sareen commented 1 year ago

Alright -- I've cleaned up a decent amount. I've made it so that you have to manually add the lib/net library if you want to use it, instead of it being always there. This allows the CI to pass. I think it looks pretty good. I'll add some more comments etc. tomorrow. Let me know if it looks good.

titzer commented 1 year ago

I think after the last two minor changes are addressed, we can land this. Thanks Kunal!

k-sareen commented 1 year ago

I think after the last two minor changes are addressed, we can land this. Thanks Kunal!

Cool! I've also made the changes regarding the array sizes. I'll push it all in one go.

k-sareen commented 1 year ago

Cool! I've also made the changes regarding the array sizes. I'll push it all in one go.

I've made the function rangeToInetAddress() public so that you can easily use it for making your own IP addresses instead of hand-crafting it. As an aside, it'd be good to add tests for it (I've on my own machine tested the generated struct layout and it exactly matches the struct layout from an equivalent C program), but we can figure that out in a future PR.

Merry Christmas! 🎉

k-sareen commented 1 year ago

This is super cool! I just ran the EchoServer on one computer and the EchoClient on another and they were able to talk to each other. Pretty fun. UDP is probably the next thing I'll add.

titzer commented 1 year ago

Thanks Kunal!