Exploring the QUIC Protocol

2716 VIEWS

The Internet is part of our lives now, and practically anything you need can be found online. If you look at the Internet’s history, you will find that its underlying core technologies have changed dramatically. The Internet Protocol Suite, or TCP/IP, has been around since the ‘70s, and it’s the backbone of the Internet. As new technologies are discovered, we have the opportunity to acquire even more powerful mobile computers and phones. Those devices have consistently improved, and their Internet connections have also steadily improved. Yet there are still a lot of obstacles in this area. (For instance, try browsing the Internet while moving in a subway—The experience is constantly interrupted by signal blackouts.)

As TCP is not going to be replaced anytime soon, the only alternative is improvements to other protocols on top of UDP. QUIC does that, and suffers no limitations imposed by operating systems or other factors. Let’s explore this more here.

Go QUIC or Go Home

QUIC aims to get the best of the UDP and TCP protocols. The main goal is to improve the perceived performance of connection-oriented web applications that are currently using TCP.

In order to decrease connection latency for an efficiently routed connection, fewer round-trips are required. For comparison, let’s see how TCP handles this in the following figure:

Figure 1. TCP 3-Way Handshake

As we can see from the above image, in order to establish a connection, there are three steps. Some overhead is required on the server side in order to keep track of the listener connection status before the first data payload is actually exchanged. This creates lots of possibilities for attacks, such as SYN Flooding, etc.

Much of the work on QUIC is concentrated on reducing the number of round-trips (RTT) required when establishing a new connection, especially around the handshake step.

The situation can be even worse with TCP+TLS connections where you have 3 RTTs for the first connection and 2 RTTs for repeating connections.

Using QUIC reduces this to 1 RTT for the first connection and 0 RTTs afterward, making it very suitable for low-latency applications. Mobile clients tend to gain a lot from this approach.

As for software support, the news is not so great. Currently, only Google servers support QUIC, and there are just a few open source implementations in Go. If you want to use it in Chrome, you need to enable support via experimental flags. For now, if you want to push it to your clients, they need to be compatible.

QUIC Server Example in Go

In this example, we are going to show how this protocol can be used to speed up the delivery of web content in cases of low or limited bandwidth connectivity. We are going to use a Go implementation of QUIC, which is quic-go.

First, make sure you’re using the latest Chrome browser and that you have enabled quic experimental support (you can do this by navigating to chrome://flags/#enable-quic and setting the value to enabled).

1. Create a folder and generate self-signed certificates for the https server:

 $ openssl req -x509 -newkey rsa:4096 -nodes \
-x509 -keyout key.pem -out cert.pem -days 365
Generating a 4096 bit RSA private key….

2. Install the dependencies:

 $ go get -u -d github.com/devsisters/goquic

3. Add the code for the example:

 $ touch main.go
 File: main.go
 package main

	  import (
	  	"flag"
	  	"fmt"
	  	"io"
	  	"log"
	  	"net/http"
	  	"path"
	  	"runtime"
	  	"strings"

	  	_ "net/http/pprof"

	  	"github.com/devsisters/goquic"
	  )

	  type binds []string

	  func (b binds) String() string {
	  	return strings.Join(b, ",")
	  }

	  func (b *binds) Set(v string) error {
	  	*b = strings.Split(v, ",")
	  	return nil
	  }

	  func init() {
	  	http.HandleFunc("/image", func(w http.ResponseWriter, r *http.Request) {
	  		// Small 40x40 png
	  		w.Write([]byte{
	  			0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
	  			0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x04, 0x03, 0x00,
	  			0x00, 0x00, 0x7e, 0xd0, 0xa5, 0x5e, 0x00, 0x00, 0x00, 0x1b, 0x50, 0x4c, 0x54, 0x45, 0xcc,
	  			0xcc, 0xcc, 0x96, 0x96, 0x96, 0xbe, 0xbe, 0xbe, 0xb1, 0xb1, 0xb1, 0xb7, 0xb7, 0xb7,
	  			0xc5, 0xc5, 0xc5, 0xaa, 0xaa, 0xaa, 0xa3, 0xa3, 0xa3, 0x9c, 0x9c, 0x9c, 0xef, 0x73, 0x10,
	  			0xb1, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0e, 0xc4, 0x00, 0x00, 0x0e,
	  			0xc4, 0x01, 0x95, 0x2b, 0x0e, 0x1b, 0x00, 0x00, 0x00, 0x36, 0x49, 0x44, 0x41, 0x54, 0x28, 0x91,
	  			0x63, 0x60, 0x18, 0x05, 0x03, 0x09, 0x98, 0x4c, 0x4a, 0xe0, 0x24, 0x1c,
	  			0x30, 0x5b, 0x44, 0xc0, 0x49, 0x38, 0x70, 0x35, 0x64, 0x64, 0x30, 0x51, 0x00, 0x91, 0x48,
	  			0x20, 0x51, 0x22, 0x82, 0x21, 0x1c, 0x4c, 0x22, 0x03, 0xa0, 0x69, 0x26, 0x0a, 0x0c, 0xa8,
	  			0x66, 0x8e, 0x02, 0x9a, 0x02, 0x00, 0xa7, 0xec, 0x07, 0x5a, 0x84, 0x75, 0x37, 0xf8,
	  			0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
	  		})
	  	})

	  	http.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) {
	  		io.WriteString(w, "

 

")
	  		for i := 0; i < 1000; i++ {
	  			fmt.Fprintf(w, ``, i)
	  		}
	  		io.WriteString(w, "")
	  	})
	  }

	  func getBuildDir() string {
	  	_, filename, _, ok := runtime.Caller(0)
	  	if !ok {
	  		panic("Failed to get current frame")
	  	}

	  	return path.Dir(filename)
	  }

	  func main() {
	  	go func() {
	  		log.Println(http.ListenAndServe("localhost:6060", nil))
	  	}()

	  	bs := binds{}
	  	flag.Var(&bs, "bind", "bind to")
	  	certPath := flag.String("certpath", getBuildDir(), "certificate directory")
	  	www := flag.String("www", "/var/www", "www data")
	  	flag.Parse()

	  	quicHdr := http.DefaultServeMux

	  	certFile := *certPath + "/cert.pem"
	  	keyFile := *certPath + "/key.pem"

	  	http.Handle("/", http.FileServer(http.Dir(*www)))

	  	if len(bs) == 0 {
	  		bs = binds{"localhost:6161"}
	  	}

	  	var err error
	  	server, err := goquic.NewServer(bs.String(), certFile, keyFile, 5, quicHdr, quicHdr, nil)
	  	if err != nil {
	  		log.Fatal(err)
	  	}

	  	if err := server.ListenAndServe(); err != nil {
	  		log.Fatal(err)
	  	}
	  }

In this example, we’ll set up two servers for the same endpoint. One is a normal HTTP server listening on localhost:6060

and the other is a QUIC server serving on

 localhost:6061

.
The endpoint they are handling is located at the

 /images

endpoint, and it will try to load 1,000 40x40px image tiles.

To start the server, just enter the following command:

 $ go run main.go -www=./

Then, navigate to http://localhost:6060/images and https://localhost:6061/images and watch how the images are loaded. You will notice a significant difference in speed when visiting the QUIC-enabled endpoint.

In Conclusion

In this article, we learned more about the QUIC protocol and the issues it tries to solve. With the passage of time, the existing Internet protocol stack has been left behind, and mobile clients are becoming more and more resource-hungry.

In our demo application, we learned that QUIC can help shove off unnecessary connections and improve the speed of data transfer between clients and servers—but universal browser adoption has a lot more miles to run, as currently only Chrome supports this protocol.

Hopefully, in the near future, QUIC will be mainstreamed and help make unbearable signal blackouts in the subway disappear once and for all!

Resources

  1. WIki article
  2. GoQuic

Theo Despoudis is a Senior Software Engineer, a consultant and an experienced mentor. He has a keen interest in Open Source Architectures, Cloud Computing, best practices and functional programming. He occasionally blogs on several publishing platforms and enjoys creating projects from inspiration. Follow him on Twitter @nerdokto. Theo is a regular contributor at Fixate IO.


Discussion

Click on a tab to select how you'd like to leave your comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Menu
Skip to toolbar