[00:00.000 --> 00:10.800] Hello everybody, this is Stefan, Stefan Bortmaier of AFNIC, we will talk about drink, which [00:10.800 --> 00:14.120] is, I guess, an experimental DNS server? [00:14.120 --> 00:16.920] No, not at all. [00:16.920 --> 00:20.200] It's a Tramway station in Belgium, actually. [00:20.200 --> 00:24.080] I hope that you explain. [00:24.080 --> 00:33.400] Well, yes, it's a DNS server, and you can see here an example of it working at FOSDEM. [00:33.400 --> 00:41.120] I ask 2plus2.diamondname txtrecord and that's extraordinary. [00:41.120 --> 00:45.720] I get four as an answer, which is really, really useful. [00:45.720 --> 00:53.160] It was not possible before, but, but, exactly, exactly, this is authentic. [00:53.160 --> 00:58.400] So you can be sure it's really, really full because it's signed with DNSSEC. [00:58.400 --> 01:05.240] So now we are going to see how it is done by Medlector. [01:05.240 --> 01:12.640] So drink is a dynamic, authoritative name server, and with several services. [01:12.640 --> 01:17.560] The main one, which was the original goal, is to return the IP address of the client. [01:17.560 --> 01:23.520] You have a lot of services on the Internet doing this, but all of them are very minimum. [01:23.520 --> 01:29.040] They don't implement all of the funny things of the DNS. [01:29.040 --> 01:35.280] We have also other services, for instance, ECS, EDNS Client Subnet Echo, can be useful [01:35.280 --> 01:42.000] also if you want to know what your resolver is sending about you, and you have other services [01:42.000 --> 01:44.000] such as Calculator. [01:45.000 --> 01:53.680] Well, the goals to develop drink were first to learn, to have fun also, and also to implement [01:53.680 --> 02:01.520] a lot of DNS stuff that are missing from the typical dynamic DNS services, such as TCP, [02:01.520 --> 02:09.960] NSID, cookies, DNSSEC, of course, et cetera, extended DNS errors, et cetera, et cetera. [02:09.960 --> 02:18.800] So that was the idea, and also it also provides a platform to test IDs at IETF Hackathon. [02:18.800 --> 02:24.720] IETF Hackathon are great because you can have t-shirts, and it's an opportunity to test [02:24.720 --> 02:32.680] new IDs, new stuff, and modifying existing software like NSD or Bind or Not is not always [02:32.680 --> 02:39.040] easy, so I wanted something which was easier, at least for me. [02:39.120 --> 02:46.960] So as you see, it does not pretend to be a competitor to things like Power DNS, NSD, [02:46.960 --> 02:48.960] Not, et cetera. [02:48.960 --> 02:51.460] It's experimental. [02:51.460 --> 02:55.720] So the implementation is done in Elixir. [02:55.720 --> 03:02.720] For the people who don't know Elixir, it's mostly a functional programming language which [03:02.800 --> 03:10.040] compiles to Erlang bytecode, which is then executed by the Erlang virtual machine. [03:10.040 --> 03:15.960] The good thing about Erlang is massive parallelism, so the virtual machine is really, really good [03:15.960 --> 03:17.960] for that. [03:17.960 --> 03:25.320] The syntax of Erlang is seen by many people as a bit of style. [03:25.320 --> 03:31.400] So the Elixir was mostly done, at least at the beginning, to have a better syntax for [03:31.400 --> 03:35.000] the same bytecode on the virtual machine. [03:35.000 --> 03:41.080] Also it's always fun to learn a new language. [03:41.080 --> 03:43.160] I didn't do everything myself. [03:43.160 --> 03:49.600] I had to rely on several existing libraries, and it's one of the pleasures of free software. [03:49.600 --> 03:59.720] You have a lot of libraries with free software lessons compatible with the one you use, hopelessly. [04:00.200 --> 04:08.640] The problem is that Elixir is not mainstream, so unlike languages like Go or Python, which [04:08.640 --> 04:17.400] have very, very good mature, maintained, debugged DNS libraries, Elixir, wow. [04:17.400 --> 04:24.000] There are some DNS libraries, typically with the last commit three or four years ago, sometimes [04:24.000 --> 04:29.440] older, and not always maintained and things like that. [04:29.440 --> 04:33.880] So it's a typical problem when you program in Elixir. [04:33.880 --> 04:41.360] When you go to X, which is the main repository of libraries, you always find something, whatever [04:41.360 --> 04:47.320] you are looking for, but pay attention, is it still maintained, debugged, et cetera. [04:47.320 --> 04:51.960] You have many libraries for the same stuff, but not all of them perfect. [04:51.960 --> 04:57.840] So it's one of the problems you have when you program in Elixir. [04:57.840 --> 05:06.560] Everything can itself call external microservices with HTTP or things like that, which as consequences [05:06.560 --> 05:12.040] for the implementation, because external services can be slow or unreliable, so you have to [05:12.040 --> 05:19.520] be careful not to crash, not to ung everything while you are waiting for the microservices. [05:19.520 --> 05:24.680] It's a bit like the talk about DNS resolution for graphical program. [05:24.680 --> 05:31.920] In Elixir, we may, unlike the typical authoritative server, which only depends on what is in its [05:31.920 --> 05:39.560] memory, so it's very predictable, and the response time is constant, unlike this typical authoritative [05:39.560 --> 05:47.200] name server, drink as a response time on success rates, which are highly dependent on the external [05:47.200 --> 05:51.000] services. [05:51.000 --> 05:55.160] That's free software, of course, because we are at FOSDEM, so I wouldn't dare to present [05:55.160 --> 05:58.840] it if it were not free software. [05:58.840 --> 06:04.840] You are here, but let's go to the important implementation point. [06:04.840 --> 06:08.480] First one, which is probably the most important, parallelism. [06:08.480 --> 06:10.040] So I don't like events. [06:10.040 --> 06:14.360] I think that events are an invention of the devil. [06:14.360 --> 06:22.040] Again, God intended parallelism to be done with processes, and Elixir, well, Erlang actually, [06:22.040 --> 06:30.240] because the run time is Erlang one, Erlang encouraged you to use massive parallelism, [06:30.240 --> 06:32.840] and when I say massive, really massive. [06:32.840 --> 06:40.040] You have anything to do, you create a new thread of execution, and it's very, very efficient. [06:40.040 --> 06:45.720] So in drink, every DNS request is a separate process. [06:45.720 --> 06:50.400] When I say process, it's not an operating system process, because of course creating [06:50.400 --> 06:54.720] them or managing them would be much too costly. [06:54.720 --> 07:00.320] But one of the funny things with the Erlang world is that they have a terminology which [07:00.320 --> 07:02.120] is quite specific. [07:02.120 --> 07:09.280] So words like process or application do not have the same meaning in the Erlang world as [07:09.280 --> 07:10.720] everywhere else. [07:10.720 --> 07:15.720] So a process here, it's what Go is calling a Go routine, for instance. [07:15.720 --> 07:18.920] For those who programmed in Go, it's more or less the same. [07:18.920 --> 07:23.480] Basically, it's very clip to create and to manage. [07:23.480 --> 07:24.720] So don't hesitate. [07:24.720 --> 07:30.280] One of the things that we always tell to the beginners in Elixir or Erlang, don't hesitate [07:30.280 --> 07:32.560] to create process. [07:32.560 --> 07:35.000] So every request is a process. [07:35.000 --> 07:42.400] When it does TCP, every TCP connection is a process. [07:42.400 --> 07:44.400] And everything is done by process. [07:44.400 --> 07:49.840] For instance, logging statistics, where it's not implemented yet, but control through a [07:49.840 --> 07:53.440] local socket is also done by a separate process. [07:53.440 --> 07:58.920] As I said, there is a process for everything. [07:58.920 --> 08:04.920] So as consequences, if you crash, if there is an exception, remember it's experimental [08:04.920 --> 08:08.520] code and it's written by me so there are a lot of bugs. [08:08.520 --> 08:10.840] But if you crash, you only crash one process. [08:10.840 --> 08:14.440] You don't take down the entire server. [08:14.440 --> 08:21.200] So that's a very interesting thing because it's one of the motto of the Erlang and Elixir [08:21.200 --> 08:24.120] programmers, let it crash. [08:24.120 --> 08:29.640] If a process crashes, it's not a big problem as long as the entire server continues to [08:29.640 --> 08:31.140] work. [08:31.140 --> 08:36.640] In the same way, if a request is stuck because you are waiting for something, you are calling [08:36.640 --> 08:41.880] a microservice somewhere at the other end of the internet and it does not reply or not [08:41.880 --> 08:50.560] immediately, it's not a big problem for drink because all the other requests will continue [08:50.560 --> 08:51.640] to work. [08:51.640 --> 08:58.640] Because parallelism is really great and unlike what many people are saying, it's even simpler [08:58.640 --> 09:00.800] than traditional programming. [09:00.800 --> 09:07.800] So for TCP, as a consequence, when I programmed it in the Elixir way, pipelining, meaning [09:07.800 --> 09:12.840] sending several requests over the TCP connection without waiting for the reply of the first [09:12.840 --> 09:18.920] one, worked immediately without me having anything to do at all. [09:18.920 --> 09:26.520] On out-of-order replies, which are not only allowed in TCP DNS but also mandated by the [09:26.520 --> 09:34.720] RFC, work also immediately the first time I tested, it worked without anything specific [09:34.720 --> 09:38.480] because every DNS request is a process. [09:38.480 --> 09:42.160] It works in parallel, so you have out-of-order replies. [09:42.160 --> 09:47.480] Remember that for a typical authoritative name server, out-of-order replies are not [09:47.480 --> 09:52.320] necessary because the response time is typically the same for every request. [09:52.320 --> 09:58.480] So there is not really any point in making out-of-order replies, unlike a resolver for [09:58.480 --> 09:59.480] instance. [09:59.480 --> 10:06.800] But drink is a bit special because any request can take some time, a lot of time. [10:06.800 --> 10:11.880] So out-of-order replies are still very important. [10:11.880 --> 10:16.440] And as I said, parallel programming is simpler, this is something you have to teach to the [10:16.440 --> 10:17.440] students. [10:17.440 --> 10:21.560] Parallel programming is not something very complicated that you see only at the end of [10:21.560 --> 10:22.560] the year. [10:22.560 --> 10:30.840] It's something very simple, very natural, and if you don't use events, everything is fine. [10:30.840 --> 10:36.240] And you don't care about things like, this request may block me, yeah, okay, let it block, [10:36.240 --> 10:42.160] no problem, other process will work. [10:42.160 --> 10:45.560] So here is an example of Elixir code. [10:45.560 --> 10:48.680] It's a functional language, so we use a map a lot. [10:48.680 --> 10:53.480] We don't do loops because loops also are an invention of the devil. [10:53.480 --> 10:59.400] So we have a set of IP addresses, and we just map a function. [10:59.400 --> 11:06.000] The function simply listen on this address with some options, okay. [11:06.000 --> 11:12.760] Then you open the socket, and for each socket, you create a server which runs this function, [11:13.200 --> 11:20.440] TCP loop acceptor, which will itself create a process for every DNS request received [11:20.440 --> 11:23.480] over the TCP connection. [11:23.480 --> 11:30.960] And that's all, and it's the end of the function that you map on the set of all IP addresses. [11:30.960 --> 11:34.880] Okay. [11:34.880 --> 11:39.600] Not even a bug in this one, no, I don't think so. [11:39.600 --> 11:44.800] Another important point when you write an internet server, whatever type of internet [11:44.800 --> 11:51.440] server it is, is of course robustness, because as you know, the internet is hostile. [11:51.440 --> 11:58.280] You see a lot of funny things, a lot of funny DNS packets, and sometimes even random binaries [11:58.280 --> 12:00.840] sent to the 53 port. [12:00.840 --> 12:07.520] So I assume everybody in the room have read LFC 9267. [12:07.520 --> 12:10.160] Is that the one that has no work? [12:10.160 --> 12:11.160] Yeah. [12:11.160 --> 12:12.160] Okay. [12:12.160 --> 12:16.360] It's very good reading if you are interested in DNS implementation, how it works. [12:16.360 --> 12:23.160] Basically, it's a list of the things that can go wrong when you pass DNS request. [12:23.160 --> 12:26.320] It's not a complete list. [12:26.320 --> 12:28.880] So the internet is a dangle. [12:28.880 --> 12:36.000] In packets can have whatever, literally whatever, everything is possible. [12:36.000 --> 12:41.600] And of course, the main example in LFC 9267 are compression pointers, because compression [12:41.600 --> 12:47.400] pointers can do things like pointing to themself, pointing outside of the packet. [12:47.400 --> 12:53.680] So if you program in C in a completely careless way, you can imagine what will happen. [12:53.680 --> 12:58.320] And indeed happens in the real world. [12:58.320 --> 13:05.120] Most of example in the LFC are from DNS mask on the windows, but it can happen to anyone. [13:05.120 --> 13:10.080] EDNS is not mentioned in the LFC, but it can be fun also. [13:10.080 --> 13:15.800] It was specially fun for me because the DNS libraries that I choose, I discovered later [13:15.800 --> 13:18.000] that it has no support for EDNS. [13:18.000 --> 13:20.240] So EDNS had to be done entirely. [13:20.240 --> 13:24.960] And EDNS options, for instance, are type length value. [13:24.960 --> 13:31.160] So you can have a length which is too large or too small and make the packet impossible [13:31.160 --> 13:37.760] to pass or even worse can trigger a crash of the server or remote code execution in [13:37.760 --> 13:38.760] the worst case. [13:38.760 --> 13:42.800] If you program in C, this is the sort of thing that can happen. [13:42.800 --> 13:46.920] So here is an example on how to pass EDNS. [13:46.920 --> 13:54.920] The second line with the brackets, the brackets are when you handle binary data, you extract [13:54.920 --> 13:55.920] the code. [13:55.920 --> 14:02.040] And you use for that pattern matching because it's a functional language, LXC relies a lot [14:02.040 --> 14:03.280] on pattern matching. [14:03.280 --> 14:07.240] So here the equal here is not an assignment. [14:07.240 --> 14:09.480] It simply means that you pattern match. [14:09.480 --> 14:12.920] And if it fails, there is an exception. [14:12.920 --> 14:20.960] So binary part which extracts the first two bytes of the data is a safe function, meaning [14:20.960 --> 14:23.880] that itself it uses pattern matching. [14:23.880 --> 14:29.920] If there are, for instance, not enough bytes to get the first two, you will have also an [14:29.920 --> 14:31.040] exception. [14:31.040 --> 14:37.280] You won't execute a remote code or go outside in the memory or things like that. [14:37.280 --> 14:39.040] Then you do things. [14:39.040 --> 14:43.040] You extract also the length of the packet and then you read the length. [14:43.040 --> 14:48.680] So if you do this sort of thing in C without paying attention, you can imagine the catastrophic [14:48.680 --> 14:50.320] thing that can happen. [14:50.320 --> 14:51.520] But here it's safe. [14:51.520 --> 14:55.840] In the worst case, you will have an exception here because not enough bytes. [14:55.840 --> 15:01.480] So here we trap the exception and we raise a proper exception and then we will return [15:01.480 --> 15:05.080] form error to the guy. [15:05.080 --> 15:10.960] In case you have something unexpected, this may crash, of course. [15:10.960 --> 15:12.760] It may take down the process. [15:12.760 --> 15:20.440] But remember, each request is a separate process, so the other request will be fine. [15:20.440 --> 15:21.440] DNSSEC. [15:21.440 --> 15:22.440] Ha! [15:22.440 --> 15:25.640] DNSSEC is fun. [15:25.640 --> 15:31.680] Because it's dynamic, you need to have dynamic signing. [15:31.680 --> 15:38.080] But cryptography, one of the things I really dislike with cryptography is that each bit [15:38.080 --> 15:42.160] wrong on the signature is completely off the mark. [15:42.160 --> 15:48.760] So it makes things really difficult to debug because some software tells you that the signature [15:48.760 --> 15:49.760] does not match. [15:49.760 --> 15:51.280] Okay, what's the problem exactly? [15:51.280 --> 15:55.040] Did I forget a field or did I forget something in the LFC? [15:55.040 --> 15:58.000] Ah, yes, something. [15:58.000 --> 16:05.160] So an example, a bug that I added, for instance, is that default encoding of the DNS library [16:05.160 --> 16:10.400] uses compression for the data which is inside the R data. [16:10.400 --> 16:15.320] So the domain name in the SOA or NS record, for instance. [16:15.320 --> 16:22.360] But the LFC about DNSSEC says that the signing has to be done on encoding which is done without [16:22.360 --> 16:24.720] any compression. [16:24.720 --> 16:29.120] So it didn't match and it took me some time to figure out what's the problem. [16:29.120 --> 16:35.880] Also the library I used did not allow to encode without name compression. [16:35.880 --> 16:39.000] So I had to redo everything myself. [16:39.000 --> 16:44.080] Like most programming projects, Drink was at the beginning, oh, it seems simple, it will [16:44.080 --> 16:46.080] be done in a weekend. [16:46.080 --> 16:50.760] And of course, in the end, it was much longer. [16:50.760 --> 16:55.320] So here is an example of code for signing, again binary data. [16:55.320 --> 17:04.560] We put all the information that are mandated by the LFC in the pseudo LFC which is then [17:04.560 --> 17:07.240] encoded by myself, unsigned. [17:07.240 --> 17:13.960] There are a few funny tricks, for instance, all domain names has to be put in lower case, [17:13.960 --> 17:19.040] the sort of problem that you discover when you go through a resolver which does case [17:19.040 --> 17:22.320] randomization. [17:22.320 --> 17:30.120] That's how you learn. [17:30.120 --> 17:34.960] But the most funny in DNSSEC is, of course, negative answers. [17:34.960 --> 17:41.320] So Moses came back from the mountain with ten commandments and one says that you should [17:41.320 --> 17:42.840] not lie. [17:42.840 --> 17:48.840] But you have to lie here because you have to say that there is nothing between this [17:48.840 --> 17:50.320] name and this name. [17:50.320 --> 17:54.160] And you don't know all the names because the server is completely dynamic. [17:54.160 --> 18:00.600] So Drink used something called white lies which are described in LFC 4470. [18:00.600 --> 18:05.960] So the Ensec record is just a bit before the name to a bit later. [18:05.960 --> 18:09.360] It seems simple, but it's very hard to get by. [18:09.360 --> 18:15.720] At one step, for instance, when implementing the algorithm of LFC, I had a code which worked [18:15.720 --> 18:20.200] with unbound or not, but failed with bind. [18:20.200 --> 18:26.720] And I never really discovered why, but after some tweaking, it worked. [18:26.720 --> 18:32.160] Also encoding of Ensec bitmaps, it's quite interesting, Ensec bitmaps are encoded in [18:32.160 --> 18:39.240] a very clever way, but very hard to get right, especially since LFC has only one test vector. [18:39.240 --> 18:43.480] So it's very difficult to see if you are on the right track or not. [18:43.480 --> 18:50.880] But in the end, it works with, we have everything in LX here necessary, enumerate, it's all [18:50.880 --> 18:53.040] the things that you can enumerate. [18:53.040 --> 18:58.640] It's a very generic library, so you can do things like finding the minimum, filtering [18:58.640 --> 19:05.520] to extract some data, map to apply a function, et cetera, et cetera, it's cool. [19:05.520 --> 19:13.160] Of course you need to test LX here like most programming languages as a framework for testing. [19:13.160 --> 19:17.800] But also I made external tests from a Python program written in Python to be sure that [19:17.800 --> 19:23.120] I don't have the same bug in both the tester and the testee. [19:23.120 --> 19:28.880] So it's also especially important in the DNS to test not only with proper DNS request, [19:28.880 --> 19:32.840] but also with broken request to see how the server reacts. [19:32.840 --> 19:38.880] So here is a Python code to create, for instance, an incorrect EDNS option. [19:38.880 --> 19:41.040] This is a comment on the second line. [19:41.040 --> 19:50.120] The length, NSID has no data, but here we put a random length, so any server that will [19:50.120 --> 19:56.960] try to decode EDNS stupidly will read too much bytes and something wrong will happen. [19:56.960 --> 20:04.760] So we create this EDNS option for DNS packet, we send it to the server and we hope that [20:04.760 --> 20:14.800] the server will reply as the RFC said with form error, otherwise the test will fail. [20:14.800 --> 20:15.960] And that's all. [20:15.960 --> 20:35.760] So time for questions. [20:35.760 --> 20:46.840] Yes, that's this. [20:46.840 --> 20:47.840] Good question. [20:47.840 --> 20:49.720] I have to think about it. [20:49.720 --> 20:56.480] The question was about byte order because DNS RFC specifies byte order for things like [20:56.480 --> 21:03.240] a length in EDNS packets, for instance, and it's not explicit in the Elixir code and that's [21:03.240 --> 21:08.480] a good question because I don't remember how I did it, but I won the program on several [21:08.480 --> 21:13.640] machines with different byte order to be sure that it was okay, but I don't remember how [21:13.640 --> 21:17.200] I did it. [21:17.200 --> 21:19.080] That's an interesting question. [21:19.080 --> 21:20.880] This is a code that I wrote. [21:20.880 --> 21:25.640] The last code that I wrote was DNSSEC, so DNSSEC is still fresh in my mind. [21:25.640 --> 21:26.640] The rest is a bit more complicated. [21:26.640 --> 21:27.640] I can probably add to that. [21:27.640 --> 21:32.640] When you specify the binary pattern matching, you can choose how you want it done, and you [21:32.640 --> 21:41.640] can specify the elements, and you've got a default in DNS, but I don't remember which [21:41.640 --> 21:54.640] input, which input. [21:54.640 --> 21:57.640] So you mentioned that when you added TCP, the pipelining just worked. [21:57.640 --> 22:01.640] How does it handle a larger plot if you do not have that? [22:01.640 --> 22:06.640] Is it always like the answer comes back and so there's no chance that a big answer has [22:06.640 --> 22:11.600] to worry about a small answer arriving while it's being sent to anything like that? [22:12.600 --> 22:18.600] So about TCP, when there are some questions or replies that are larger than other or takes [22:18.600 --> 22:21.600] more time. [22:21.600 --> 22:26.600] So because of the parallelism and because every DNS request is a separate process, they [22:26.600 --> 22:28.600] follow their own path. [22:28.600 --> 22:33.600] The only case where they meet is when they try to send the reply back. [22:33.600 --> 22:38.600] So in that case, it's a long virtual machine which is in charge of being sure that you [22:38.600 --> 22:40.600] cannot interrupt a white operation. [22:40.600 --> 22:44.600] So the way it's implemented is that everything goes through a process. [22:44.600 --> 22:46.600] For instance, logging works the same way. [22:46.600 --> 22:51.600] We send everything to a logging process which then serializes. [22:51.600 --> 22:53.600] So we can be sure. [22:53.600 --> 22:58.600] And also, writing on the socket is done by the Erlang library, not by me, so it cannot [22:58.600 --> 23:04.600] be interrupted, so there is no risk of interleaving replies, if that was your question. [23:04.600 --> 23:11.600] On the Erlang socket library also does a few things that are not really important but [23:11.600 --> 23:12.600] are fun. [23:12.600 --> 23:18.600] For instance, when creating the socket, maybe you notice this option, packet 2. [23:18.600 --> 23:24.600] It means that two bytes length has to be added automatically, which is good for EPP or for [23:24.600 --> 23:25.600] DNS. [23:25.600 --> 23:30.600] And also by default, it's in network byte order, which is good again. [23:35.600 --> 23:38.600] Oh, performance. [23:38.600 --> 23:40.600] Yes, with DNS perf. [23:40.600 --> 23:43.600] And I compare the drink with NSD. [23:43.600 --> 23:49.600] Drink is typically three to four times slower, which is expected, of course, because it's [23:49.600 --> 23:50.600] dynamic. [23:50.600 --> 23:54.600] It has not been optimized for speed, and because NSD is very fast. [23:54.600 --> 23:58.600] So of course, as you know, performance testing is something complicated. [23:58.600 --> 24:00.600] It depends on a lot of things. [24:00.600 --> 24:07.600] So I don't have strong, serious measurements, but the measurements I did on my machine show [24:07.600 --> 24:11.600] that the difference in performance is, in my opinion, quite acceptable. [24:11.600 --> 24:15.600] Three times slower than NSD is actually quite good. [24:16.600 --> 24:31.600] The question is, do I plan to add some caching in it because some questions can take time [24:31.600 --> 24:34.600] to retrieve or to compute? [24:34.600 --> 24:35.600] No. [24:35.600 --> 24:41.600] It's don't think it's, as you know, caching is one of the two or three complicated things [24:41.600 --> 24:43.600] in computer programming. [24:43.600 --> 24:48.600] So in my opinion, it's not worth it. [24:48.600 --> 24:52.600] Caching can be done by the client, anyway. [24:52.600 --> 24:58.600] Or you can run the drink behind the NSD, if you will insist. [24:58.600 --> 25:00.600] Thank you, Stefa.