So now we have Glenn, he's going to talk about his project of Rama and yeah, it looks like
he's going to be really good talk, so give me a welcome please.
Okay, first of all, does everybody hear me?
Okay, super. Before we begin, did anybody hear already hear about Rama?
Or is it the first time a one person?
That's something.
So Rama is made by myself and the company I co-founded and we mainly built the company to
fund our open source work.
As most of our work is open source and freely available in the MIT licenses,
because we believe that there needs to be better software to be easier building network
stacks.
And we will talk about that in this conversation.
So we also have a podcast about networking and rust.
In case you like the subject, I imagine you are in the rust track.
And the reason I bring it up is because we don't only want to like develop software.
We also want to educate people around rust and networking because for too many in the
software industry, networking is still a mystery.
Even if you would try to write back-and-services, you might never have,
yeah, read the protocol RFCs or then how they work or even make the best use of it possible.
So in case you like networking a rust, I would tell you you might want to listen to the podcast.
And in case you do not work, I want to have a small interview after this conversation
do catch me outside.
So we develop Rama, it's on Gipter, it's MIT license.
And it's a modular service framework to move and transform packets.
So we're going to try to unpack that tagline in this talk.
So why Rama?
That is the first question because of course it's not like it's the only framework around building network stacks
or why another one.
And the honest answer is I wish I wouldn't have to write it.
Because it's the last three, four years.
There was a lot of time, so I'm going into.
But I feel I know the choice and I will tell you why.
So you can, if you want to build network stacks, either use something that is of the
shelf working.
So meaning like something like ng and x or maybe you use MIT and proxy,
if you build proxies, there are all kinds of things.
And you might try to plug in for them.
You might try, yeah, ways to extend them.
But you're always, first of all, it's very implicit.
So you don't really know often how the flow is going.
What the lifetime of the request is, what you can do.
And even though it might give you strong velocity at start because it has a lot for you already.
You will quite quickly run to a wall, once you build up your service more or more.
As you build up new features and the complexity at up,
you will soon or later start to fight against your own tooling and your velocity will drop.
The other choice you have, and that's the one I also choose in the past often.
Next to having written an engine x before, is you rise from scratch.
And of course when I mean writing from scratch, I don't mean you start from nothing.
You will probably use some like low level networking library.
If you use risk time, sure there are a couple you can think of and see there are other ones.
But the problem with that is that each time you do this, be it for a customer or for your own product,
you each time again run in very similar issues and books.
Because while the protocols themselves are very easy,
it is very tricky to get them right once you combine them.
And once you throw in around 50,000 or 100,000 requests per second on these kind of services,
you quite quickly run into all kinds of applications which you didn't foresee.
And I've been doing that for around eight years or more.
And it became very tiring because first of all you have like a very low velocity at the start.
And secondly, you each time like a set run in the same issues.
The only nice thing about writing from scratch is that you do have the ultimate freedom to do what you want.
But the downside is that you just reinventing the village time.
And so, while I also like about the ability to write from scratch or that approach,
even though I wouldn't recommend it, is that you can write in the way where the flow is very explicit.
Because if we go back to the of the south regions like NGNX or very similar technologies to me,
it is not always clear how a request flows through which middleware, through which plugin,
especially won't you start to write more and more dynamic plugins in CEO or whatever.
Like it's very unclear, like how your request flows and when some weird issues happen,
in some kind of network stack layer, be it TCP, UDP, NHDP, doesn't really matter.
You really have to stretch your head to find where the issue is.
So, with Rama, we really wanted to have something that won't really read the code,
that you write yourself with Rama, that you also understand how the packets flow.
So, if you look, for example, at an example of such a stack, this is an example of a service stack,
which is combining TCP and on top of that, we use Sox5 or HTTP, like both proxy protocols,
would be supported in this stack, and it is also optionally with TLS,
and it is like a little proxy that then proxies the either plain text traffic or the encrypted traffic,
but it leaves that encrypted.
And while it might be with daunting at first, the flow is very clear.
Like even though you don't or not an expert in, let's say, the framework,
if you take some time to reach through it from bottom to upper or up to bottom,
it will make sense how the flow is. You don't have to guess it.
You don't have to look in some documentation.
Is it going first, you have this middle layer or if you have that middle layer,
is the ordering like this or like that, you can just read it.
But I do understand that it will look a bit daunting,
but that's simply the nature of the complexity of network stacks.
So we are not, of course, invented in a vacuum, we are built on the shoulders of giants,
and in our case, we built in the philosophy of towers,
and tower itself was built on top of the philosophy of something else.
And that means that everything around is basically a service.
So you get an input and you give back an output or an error.
And of course, in case you have no error in reverse,
you can use something like infallible, that also works.
And everything that you want to layer be at the laser HTTP or whatever,
will just layer on top of each other.
And this way, you can combine them like Lego blocks,
and have the absolute freedom of your network stack.
The service trade currently looks like this.
If you ever have seen tower, it will look very similar.
So you have to define an output, you have to define an error.
And you have to define, of course, the serve function.
You don't always have to define this yourself,
or implement this straight yourself.
There are other ways to get the same service,
be it with a function or be it as something else,
like static response or something.
And then because of how risk and the status today,
we also need to provide something to provide the box service,
because we already used implementation,
return type for the trades,
and that is currently a bit wonky in the rest state as it is.
But we did want to make use of it,
and so we were very happy that it shipped as part of the 2024 edition,
as we've been following risks since the very early days,
like around 10 years ago.
Now, as I said, we didn't invent this service concept.
As far as I remember, it was a phenical,
who first came up with it at Twitter,
and maybe even before, but I remember in 2011,
they made an announcement that they started using phenical,
which was in Java, if I recall correctly,
and then later in Rust, there was the folks of tower or S.
They made tower, and it looked,
I mean, they took a lot of the knowledge from phenical,
and made it in a more like Rust way.
The Rust of 2016 is very different than Rust is today.
Back then, there were no easy ways to do a synchronous code in Rust,
and most didn't even attempt to do it.
And so you can also see it in how tower is built.
On top of that, they made some choices that we find regrettable,
such as the fact that they made it very conscious choice
to add something like pull ready in their service trade,
which means even though it's a kind of function that you need,
in case you want to check if a service is ready,
and it might be needed because you want to load balancing
or shedding or whatever, but it's a very niche use case,
or many ways to do it also even if you don't have an explicit function.
And next to that, in case you want to ever not make the function mutable,
it is also a very wonky contract to get right.
So we then, in 2018, start the experiment with something called tower or sink,
as at that time, in nightly, all the things that we wanted were available,
and that is what you see below the tower implementation.
So we dropped the pole ready, and we started to return futures,
and then between 2024 and 2006, we started to develop Rama
after we have done several iterations on our several experiments and MPPs.
And that is currently how the trade is today.
So we also renamed the request and response to input and output,
because in Rama everything is a service,
and so even your TCP service, or your UDP service, your TLS service,
your SOx5, your H Proxy, it doesn't really matter.
There are all services that you stack on top of each other,
in a way that you want and in a way that works for your project.
So as I said, you can see several examples for TCP,
it means that you are getting a TCP stream,
and you just return nothing because you are serving the entire stream.
For TLS, you will get some kind of stream,
it doesn't have to be TCP, it can also be UDP or whatever kind of stream it is.
It just needs to be a stream that gives you bytes,
and then HTTP will start from a byte stream that's your server,
but then you also have an HTTP service,
which actually deals with the requests.
And once you read that layer,
it doesn't really matter anymore if it HTTP 2 or HTTP 3 or whatever.
At that point, it's just a generic kind of request.
So if we now go back to the original slides,
where I showed you an example stack,
which you can also find in our examples,
and I will talk a bit about Rama examples later,
is that you can see indeed,
that we have a kind of protocol,
the layers nicely on top of each other.
However, and that is the first thing that I noticed,
and it might be because some people didn't work a lot with Rost before,
but one thing I see a lot is then,
you get into, and as not only Rama,
I also saw it in other communities like in Axium and in Towers,
is that they start to get very huge stacks of generics,
and they try to label them.
So for example, for this stack,
that is the entire type,
and it's very painful, and in fact,
it's only painful because you try to label it.
And I would think it's kind of like trying to label
every part of your house,
like you wouldn't do that.
You might define some properties of your house,
but you're not going to define every little
cranny and who can detail of your house.
So instead, you have to think about,
what is the actual service doing?
For example, in this case, or stack,
was a proxy stack, which was a man in the middle
in HTTP traffic via the other encryption notes,
and so we really just want a TCP stream at input,
and we want an error in case something really horrible and wrong,
or otherwise nothing because we were just finished with serving this stream.
And in that way, you have to think in terms of trade bonds,
because one should start to that,
then you have two options.
Either you have your functions or your structure or whatever,
where you take a generic parameter like a single one,
and you just define what you want.
Be it, okay, for example, in this case,
I want the service, which takes an HTTP request,
and it returns an HTTP response,
and the error needs to be something that some kind of standard error
or can be turned into that.
Or in case you don't want to deal with generics,
because the thing with generics is that they're very leaky,
and once you start to introduce them to your struct,
for functions as less of an issue,
because you rarely ever have to define them,
but for structs they can be leaky,
and in case you need to pause them around
to several locations,
you might as that want to choose for dynamic dispatching.
And that is the same thing,
because your trade bonds still looks the same.
The only difference is that you boxed it,
and in an OR case,
we didn't box it to be octed,
as dynamic dispatched trades.
Now, around case and simplicity,
and also, of course, and being explicit.
This wasn't like a very easy road,
and a similar road that's finished far from it.
Like we are now trees and developments,
and I feel given how many network protocols there are,
or many different use cases there are,
I might still be doing this for decades to come.
So far, we already have more and more companies adopting Rama,
and this tells us a lot about how to improve Rama,
and what to add next.
Some of these, which we thought of initially as working fine,
they're not to be too complex or too niche,
very similar to how we observed the pull ready function,
to be too niche over use case,
to warn that kind of complexity.
And so I want to give you a couple of examples,
because at Plabayo,
we really want to develop software that works for you,
and that's not in your way.
So here are a couple of examples.
If you take a service,
and you need to have it cloned,
because you need to pass it through different threats,
you could implicitly market for the users.
And that's something we did at some locations at the start,
because it made it very easy,
or it made sense at that point of time.
I don't remember any more,
but as we are cleaning up more and more of these kind of initial choices,
I find it a much better choice to instance,
ask the service needs to be a service,
but it also needs to be cloneable.
If the user wants to market, that's fine,
but it needs to be a choice of the users,
or the choice of the thing the user is using.
Similarly, we have the philosophy of everything as a service,
but still we had sometimes specialized traits
to do something specific.
An example of that was the request inspector,
and it kind of does what the name does.
It is kind of like a service,
but it takes a request,
and it returns a modified request.
And it's a very kind of niche use case,
but it did work out,
and it made it another time.
But again, as we start to clean up our code base,
and get more and more traction in production,
we also wanted things to be simple,
because there was already sufficiently things to learn.
Why should use more complexity for the users?
And so we removed that,
and now these things are just, of course, a service,
because you can just modify your request
and leave the response, and it ends the same.
Another example is something called a context.
So initially, the service didn't just take input,
but it also kind of context.
And the idea behind that was to make statically type states,
pass around, but it turns out that it is a very niche use case,
and in the only location you really want this,
is in some kind of servers.
And so now you can still do these things,
and of course you could always do these things,
because services can be structs,
and so you always have the option
to make your own struct implement service traits,
and add state to it.
But in case you want to have something like a function,
as your router, in case you write an HTTP stack,
then we allow the things in the router to have a state,
and we pause them via a trade bound,
and then you are statically type state still works,
but it is a very niche use case,
and it is very located for this kind of use case,
and we don't have to leak it through the entire thing.
Sometimes you do have dynamic states,
and for that we have extensions.
So both inputs and outputs kind of extensions,
and you can think of that for your so-called addresses,
for dynamic behavior, maybe you want to disable IPv4,
maybe you want to override the NS.
There are all kinds of things that you might want to plug in,
and at your middleware, including your own middleware,
good react to.
That's what extensions are for.
And so for now it's influx,
because we are improving it once again.
But at the moment, this is having two traits on the inputs,
and the outputs, which is like you can take them by reference,
taking them by a multiple pointer,
but in the future you will only need to do it by reference,
because we just finished implementing an append-only vector,
which allows us to pass only by single shared reference,
and don't have to worry about a mutation.
But the point being,
is you can inject any kind of dynamic state into it,
and the only unique thing is to put its sockets,
all the way to your final application handling of the response,
whatever protocols you go through,
they all change these extensions as they go,
and they leave little breadcrumbs,
as what choices we're made,
and what behavior has to have dynamically be changed.
So Rama is a proxy,
but it's also not just a proxy framework,
because a proxy is a server and a client.
And so what I mean with that is,
for example, if you look at a client or a server HTTP,
and a mostly used HTTP here,
because it's the protocol,
but of course not just a HTTP,
but if we continue to take an HTTP,
you can see that the client and a server,
they are pretty much the same signature.
You take a request and you return a response,
and that is a very powerful concept,
once you get your hat around it,
we'll call it kind of means that we can make any service,
like a very highly level,
by having a trade and extension trade,
and it's also not the concept we invented,
and extension trade is basically,
from very generic bonds,
be it like any service,
and you give it some kind of capabilities.
And this allows us, like in the left,
you can see,
to easily make a request,
and this works on any service,
meaning even if you want to test your web servers,
you can just treat it as if it's a client,
because everything is really just a service.
So Rama is a bunch of crates,
and it's only growing,
but for users that use Rama,
they will just use the mono create,
which imports all the other ones behind feature flags.
So we have things around crypto,
TLS,
around HTTP,
web sockets,
GRPC,
which is double an HTTP,
well,
does not have to be,
but in most cases it is,
you have the NAS,
you have transports,
whatever.
And this list is growing,
as the needs of ourselves,
and the needs of our customers grow.
We also have other resources,
such as the book,
there you can learn more about
or philosophies or patterns,
maybe because you're new to networking,
you can also learn about the different kind of proxies you can build,
and of course,
just to give you a taste,
because in the end you kind of can build with yourself.
We also have a CLI tool,
we also have other things.
One example,
for example,
in the book,
you can read about errors,
and the fact that you can attach context to it,
which is something most people would use anyhow for,
but in Rama,
we have a mini version of that,
which allows you to,
to work with errors without having to add that,
and other dependency to your list.
We also have public services,
where you can test your,
some HTTP protocol,
should you be implementing my client,
or what do you want to see,
how your traffic looks like on the LSTTP,
or HTTP.
We also have a fingerprinting service
to do emulation of user agents.
We have an IP service,
and these are all freely available.
You can check them out now.
We also have a lot of examples,
and we add more and more examples.
All our examples are also tested,
and I don't just mean they compile.
We also actually test the behavior for every commit,
and know that they will work as promised,
including documentation on the top of the example, etc.
Now, Rama has been going on for a long time,
and so we are hoping to ship zero point three,
somewhere in the next month,
but of course that is to be seen.
There are a couple of little things
that we want to know before,
but starting from that,
we are a bit tired of having the long gaps
between the releases and having many alpha versions.
So we are now at a point where we are stable enough,
that we want to start doing regular releases.
We will still do maybe sometimes breaking changes,
but there will be more frequent,
instead of building up to a giant release,
because if you still be on 0.2,
and you have to then jump to 0.3,
it's going to be really painful,
unless you have the help from us.
Our star history grew kind of like around the time
that also our framework became more stable,
but it's still very small compared to the people that uses.
Some people work with us, but others not.
For example, OpenAI, they use it also for their codecs.
So if you use codecs then it's like a local network proxy built with Rama.
We have fund this work.
And of course sharing is caring.
The final thing I want to mention about Rama
is we really were tired of reinventing everybody,
which is very different.
And if everybody makes their own little island,
it just doesn't work.
And so that's, we welcome anybody in a community.
We also do mentorship, et cetera.
And so yeah, I don't think I have time for questions,
but otherwise thank you very much for paying attention to my talk.
And if you want to talk to me, you can meet me outside.
Thank you.