• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1package nghttp2
2
3import (
4	"bufio"
5	"bytes"
6	"context"
7	"crypto/tls"
8	"encoding/binary"
9	"errors"
10	"fmt"
11	"io"
12	"net"
13	"net/http"
14	"net/http/httptest"
15	"net/url"
16	"os"
17	"os/exec"
18	"sort"
19	"strconv"
20	"strings"
21	"syscall"
22	"testing"
23	"time"
24
25	"github.com/tatsuhiro-t/go-nghttp2"
26	"golang.org/x/net/http2"
27	"golang.org/x/net/http2/hpack"
28	"golang.org/x/net/websocket"
29)
30
31const (
32	serverBin  = buildDir + "/src/nghttpx"
33	serverPort = 3009
34	testDir    = sourceDir + "/integration-tests"
35	logDir     = buildDir + "/integration-tests"
36)
37
38func pair(name, value string) hpack.HeaderField {
39	return hpack.HeaderField{
40		Name:  name,
41		Value: value,
42	}
43}
44
45type serverTester struct {
46	cmd           *exec.Cmd // test frontend server process, which is test subject
47	url           string    // test frontend server URL
48	t             *testing.T
49	ts            *httptest.Server // backend server
50	frontendHost  string           // frontend server host
51	backendHost   string           // backend server host
52	conn          net.Conn         // connection to frontend server
53	h2PrefaceSent bool             // HTTP/2 preface was sent in conn
54	nextStreamID  uint32           // next stream ID
55	fr            *http2.Framer    // HTTP/2 framer
56	headerBlkBuf  bytes.Buffer     // buffer to store encoded header block
57	enc           *hpack.Encoder   // HTTP/2 HPACK encoder
58	header        http.Header      // received header fields
59	dec           *hpack.Decoder   // HTTP/2 HPACK decoder
60	authority     string           // server's host:port
61	frCh          chan http2.Frame // used for incoming HTTP/2 frame
62	errCh         chan error
63}
64
65type options struct {
66	// args is the additional arguments to nghttpx.
67	args []string
68	// handler is the handler to handle the request.  It defaults
69	// to noopHandler.
70	handler http.HandlerFunc
71	// connectPort is the server side port where client connection
72	// is made.  It defaults to serverPort.
73	connectPort int
74	// tls, if set to true, sets up TLS frontend connection.
75	tls bool
76	// tlsConfig is the client side TLS configuration that is used
77	// when tls is true.
78	tlsConfig *tls.Config
79	// tcpData is additional data that are written to connection
80	// before TLS handshake starts.  This field is ignored if tls
81	// is false.
82	tcpData []byte
83	// quic, if set to true, sets up QUIC frontend connection.
84	// quic implies tls = true.
85	quic bool
86}
87
88// newServerTester creates test context.
89func newServerTester(t *testing.T, opts options) *serverTester {
90	if opts.quic {
91		opts.tls = true
92	}
93
94	if opts.handler == nil {
95		opts.handler = noopHandler
96	}
97	if opts.connectPort == 0 {
98		opts.connectPort = serverPort
99	}
100
101	ts := httptest.NewUnstartedServer(opts.handler)
102
103	var args []string
104	var backendTLS, dns, externalDNS, acceptProxyProtocol, redirectIfNotTLS, affinityCookie, alpnH1 bool
105
106	for _, k := range opts.args {
107		switch k {
108		case "--http2-bridge":
109			backendTLS = true
110		case "--dns":
111			dns = true
112		case "--external-dns":
113			dns = true
114			externalDNS = true
115		case "--accept-proxy-protocol":
116			acceptProxyProtocol = true
117		case "--redirect-if-not-tls":
118			redirectIfNotTLS = true
119		case "--affinity-cookie":
120			affinityCookie = true
121		case "--alpn-h1":
122			alpnH1 = true
123		default:
124			args = append(args, k)
125		}
126	}
127	if backendTLS {
128		nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{})
129		// According to httptest/server.go, we have to set
130		// NextProtos separately for ts.TLS.  NextProtos set
131		// in nghttp2.ConfigureServer is effectively ignored.
132		ts.TLS = new(tls.Config)
133		ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2")
134		ts.StartTLS()
135		args = append(args, "-k")
136	} else {
137		ts.Start()
138	}
139	scheme := "http"
140	if opts.tls {
141		scheme = "https"
142		args = append(args, testDir+"/server.key", testDir+"/server.crt")
143	}
144
145	backendURL, err := url.Parse(ts.URL)
146	if err != nil {
147		t.Fatalf("Error parsing URL from httptest.Server: %v", err)
148	}
149
150	// URL.Host looks like "127.0.0.1:8080", but we want
151	// "127.0.0.1,8080"
152	b := "-b"
153	if !externalDNS {
154		b += fmt.Sprintf("%v;", strings.Replace(backendURL.Host, ":", ",", -1))
155	} else {
156		sep := strings.LastIndex(backendURL.Host, ":")
157		if sep == -1 {
158			t.Fatalf("backendURL.Host %v does not contain separator ':'", backendURL.Host)
159		}
160		// We use awesome service nip.io.
161		b += fmt.Sprintf("%v.nip.io,%v;", backendURL.Host[:sep], backendURL.Host[sep+1:])
162	}
163
164	if backendTLS {
165		b += ";proto=h2;tls"
166	}
167	if dns {
168		b += ";dns"
169	}
170
171	if redirectIfNotTLS {
172		b += ";redirect-if-not-tls"
173	}
174
175	if affinityCookie {
176		b += ";affinity=cookie;affinity-cookie-name=affinity;affinity-cookie-path=/foo/bar"
177	}
178
179	noTLS := ";no-tls"
180	if opts.tls {
181		noTLS = ""
182	}
183
184	var proxyProto string
185	if acceptProxyProtocol {
186		proxyProto = ";proxyproto"
187	}
188
189	args = append(args, fmt.Sprintf("-f127.0.0.1,%v%v%v", serverPort, noTLS, proxyProto), b,
190		"--errorlog-file="+logDir+"/log.txt", "-LINFO")
191
192	if opts.quic {
193		args = append(args,
194			fmt.Sprintf("-f127.0.0.1,%v;quic", serverPort),
195			"--no-quic-bpf")
196	}
197
198	authority := fmt.Sprintf("127.0.0.1:%v", opts.connectPort)
199
200	st := &serverTester{
201		cmd:          exec.Command(serverBin, args...),
202		t:            t,
203		ts:           ts,
204		url:          fmt.Sprintf("%v://%v", scheme, authority),
205		frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort),
206		backendHost:  backendURL.Host,
207		nextStreamID: 1,
208		authority:    authority,
209		frCh:         make(chan http2.Frame),
210		errCh:        make(chan error),
211	}
212
213	st.cmd.Stdout = os.Stdout
214	st.cmd.Stderr = os.Stderr
215
216	if err := st.cmd.Start(); err != nil {
217		st.t.Fatalf("Error starting %v: %v", serverBin, err)
218	}
219
220	retry := 0
221	for {
222		time.Sleep(50 * time.Millisecond)
223
224		conn, err := net.Dial("tcp", authority)
225		if err == nil && opts.tls {
226			if len(opts.tcpData) > 0 {
227				if _, err := conn.Write(opts.tcpData); err != nil {
228					st.Close()
229					st.t.Fatal("Error writing TCP data")
230				}
231			}
232
233			var tlsConfig *tls.Config
234			if opts.tlsConfig == nil {
235				tlsConfig = new(tls.Config)
236			} else {
237				tlsConfig = opts.tlsConfig.Clone()
238			}
239			tlsConfig.InsecureSkipVerify = true
240			if alpnH1 {
241				tlsConfig.NextProtos = []string{"http/1.1"}
242			} else {
243				tlsConfig.NextProtos = []string{"h2"}
244			}
245			tlsConn := tls.Client(conn, tlsConfig)
246			err = tlsConn.Handshake()
247			if err == nil {
248				conn = tlsConn
249			}
250		}
251		if err != nil {
252			retry++
253			if retry >= 100 {
254				st.Close()
255				st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid")
256			}
257			continue
258		}
259		st.conn = conn
260		break
261	}
262
263	st.fr = http2.NewFramer(st.conn, st.conn)
264	st.enc = hpack.NewEncoder(&st.headerBlkBuf)
265	st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) {
266		st.header.Add(f.Name, f.Value)
267	})
268
269	return st
270}
271
272func (st *serverTester) Close() {
273	if st.conn != nil {
274		st.conn.Close()
275	}
276	if st.cmd != nil {
277		done := make(chan struct{})
278		go func() {
279			if err := st.cmd.Wait(); err != nil {
280				st.t.Errorf("Error st.cmd.Wait() = %v", err)
281			}
282			close(done)
283		}()
284
285		if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil {
286			st.t.Errorf("Error st.cmd.Process.Signal() = %v", err)
287		}
288
289		select {
290		case <-done:
291		case <-time.After(10 * time.Second):
292			if err := st.cmd.Process.Kill(); err != nil {
293				st.t.Errorf("Error st.cmd.Process.Kill() = %v", err)
294			}
295			<-done
296		}
297	}
298	if st.ts != nil {
299		st.ts.Close()
300	}
301}
302
303func (st *serverTester) readFrame() (http2.Frame, error) {
304	go func() {
305		f, err := st.fr.ReadFrame()
306		if err != nil {
307			st.errCh <- err
308			return
309		}
310		st.frCh <- f
311	}()
312
313	select {
314	case f := <-st.frCh:
315		return f, nil
316	case err := <-st.errCh:
317		return nil, err
318	case <-time.After(5 * time.Second):
319		return nil, errors.New("timeout waiting for frame")
320	}
321}
322
323type requestParam struct {
324	name        string              // name for this request to identify the request in log easily
325	streamID    uint32              // stream ID, automatically assigned if 0
326	method      string              // method, defaults to GET
327	scheme      string              // scheme, defaults to http
328	authority   string              // authority, defaults to backend server address
329	path        string              // path, defaults to /
330	header      []hpack.HeaderField // additional request header fields
331	body        []byte              // request body
332	trailer     []hpack.HeaderField // trailer part
333	httpUpgrade bool                // true if upgraded to HTTP/2 through HTTP Upgrade
334	noEndStream bool                // true if END_STREAM should not be sent
335}
336
337// wrapper for request body to set trailer part
338type chunkedBodyReader struct {
339	trailer        []hpack.HeaderField
340	trailerWritten bool
341	body           io.Reader
342	req            *http.Request
343}
344
345func (cbr *chunkedBodyReader) Read(p []byte) (n int, err error) {
346	// document says that we have to set http.Request.Trailer
347	// after request was sent and before body returns EOF.
348	if !cbr.trailerWritten {
349		cbr.trailerWritten = true
350		for _, h := range cbr.trailer {
351			cbr.req.Trailer.Set(h.Name, h.Value)
352		}
353	}
354	return cbr.body.Read(p)
355}
356
357func (st *serverTester) websocket(rp requestParam) *serverResponse {
358	urlstring := st.url + "/echo"
359
360	config, err := websocket.NewConfig(urlstring, st.url)
361	if err != nil {
362		st.t.Fatalf("websocket.NewConfig(%q, %q) returned error: %v", urlstring, st.url, err)
363	}
364
365	config.Header.Add("Test-Case", rp.name)
366	for _, h := range rp.header {
367		config.Header.Add(h.Name, h.Value)
368	}
369
370	ws, err := websocket.NewClient(config, st.conn)
371	if err != nil {
372		st.t.Fatalf("Error creating websocket client: %v", err)
373	}
374
375	if _, err := ws.Write(rp.body); err != nil {
376		st.t.Fatalf("ws.Write() returned error: %v", err)
377	}
378
379	msg := make([]byte, 1024)
380	var n int
381	if n, err = ws.Read(msg); err != nil {
382		st.t.Fatalf("ws.Read() returned error: %v", err)
383	}
384
385	res := &serverResponse{
386		body: msg[:n],
387	}
388
389	return res
390}
391
392func (st *serverTester) http1(rp requestParam) (*serverResponse, error) {
393	method := "GET"
394	if rp.method != "" {
395		method = rp.method
396	}
397
398	var body io.Reader
399	var cbr *chunkedBodyReader
400	if rp.body != nil {
401		body = bytes.NewBuffer(rp.body)
402		if len(rp.trailer) != 0 {
403			cbr = &chunkedBodyReader{
404				trailer: rp.trailer,
405				body:    body,
406			}
407			body = cbr
408		}
409	}
410
411	reqURL := st.url
412
413	if rp.path != "" {
414		u, err := url.Parse(st.url)
415		if err != nil {
416			st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err)
417		}
418		u.Path = ""
419		u.RawQuery = ""
420		reqURL = u.String() + rp.path
421	}
422
423	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
424	defer cancel()
425
426	req, err := http.NewRequestWithContext(ctx, method, reqURL, body)
427	if err != nil {
428		return nil, err
429	}
430	for _, h := range rp.header {
431		req.Header.Add(h.Name, h.Value)
432	}
433	req.Header.Add("Test-Case", rp.name)
434	if cbr != nil {
435		cbr.req = req
436		// this makes request use chunked encoding
437		req.ContentLength = -1
438		req.Trailer = make(http.Header)
439		for _, h := range cbr.trailer {
440			req.Trailer.Set(h.Name, "")
441		}
442	}
443	if err := req.Write(st.conn); err != nil {
444		return nil, err
445	}
446	resp, err := http.ReadResponse(bufio.NewReader(st.conn), req)
447	if err != nil {
448		return nil, err
449	}
450	respBody, err := io.ReadAll(resp.Body)
451	if err != nil {
452		return nil, err
453	}
454	resp.Body.Close()
455
456	res := &serverResponse{
457		status:    resp.StatusCode,
458		header:    resp.Header,
459		body:      respBody,
460		connClose: resp.Close,
461	}
462
463	return res, nil
464}
465
466func (st *serverTester) http2(rp requestParam) (*serverResponse, error) {
467	st.headerBlkBuf.Reset()
468	st.header = make(http.Header)
469
470	var id uint32
471	if rp.streamID != 0 {
472		id = rp.streamID
473		if id >= st.nextStreamID && id%2 == 1 {
474			st.nextStreamID = id + 2
475		}
476	} else {
477		id = st.nextStreamID
478		st.nextStreamID += 2
479	}
480
481	if !st.h2PrefaceSent {
482		st.h2PrefaceSent = true
483		fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
484		if err := st.fr.WriteSettings(); err != nil {
485			return nil, err
486		}
487	}
488
489	res := &serverResponse{
490		streamID: id,
491	}
492
493	streams := make(map[uint32]*serverResponse)
494	streams[id] = res
495
496	if !rp.httpUpgrade {
497		method := "GET"
498		if rp.method != "" {
499			method = rp.method
500		}
501		_ = st.enc.WriteField(pair(":method", method))
502
503		scheme := "http"
504		if rp.scheme != "" {
505			scheme = rp.scheme
506		}
507		_ = st.enc.WriteField(pair(":scheme", scheme))
508
509		authority := st.authority
510		if rp.authority != "" {
511			authority = rp.authority
512		}
513		_ = st.enc.WriteField(pair(":authority", authority))
514
515		path := "/"
516		if rp.path != "" {
517			path = rp.path
518		}
519		_ = st.enc.WriteField(pair(":path", path))
520
521		_ = st.enc.WriteField(pair("test-case", rp.name))
522
523		for _, h := range rp.header {
524			_ = st.enc.WriteField(h)
525		}
526
527		err := st.fr.WriteHeaders(http2.HeadersFrameParam{
528			StreamID:      id,
529			EndStream:     len(rp.body) == 0 && len(rp.trailer) == 0 && !rp.noEndStream,
530			EndHeaders:    true,
531			BlockFragment: st.headerBlkBuf.Bytes(),
532		})
533		if err != nil {
534			return nil, err
535		}
536
537		if len(rp.body) != 0 {
538			// TODO we assume rp.body fits in 1 frame
539			if err := st.fr.WriteData(id, len(rp.trailer) == 0 && !rp.noEndStream, rp.body); err != nil {
540				return nil, err
541			}
542		}
543
544		if len(rp.trailer) != 0 {
545			st.headerBlkBuf.Reset()
546			for _, h := range rp.trailer {
547				_ = st.enc.WriteField(h)
548			}
549			err := st.fr.WriteHeaders(http2.HeadersFrameParam{
550				StreamID:      id,
551				EndStream:     true,
552				EndHeaders:    true,
553				BlockFragment: st.headerBlkBuf.Bytes(),
554			})
555			if err != nil {
556				return nil, err
557			}
558		}
559	}
560loop:
561	for {
562		fr, err := st.readFrame()
563		if err != nil {
564			return res, err
565		}
566		switch f := fr.(type) {
567		case *http2.HeadersFrame:
568			_, err := st.dec.Write(f.HeaderBlockFragment())
569			if err != nil {
570				return res, err
571			}
572			sr, ok := streams[f.FrameHeader.StreamID]
573			if !ok {
574				st.header = make(http.Header)
575				break
576			}
577			sr.header = cloneHeader(st.header)
578			var status int
579			status, err = strconv.Atoi(sr.header.Get(":status"))
580			if err != nil {
581				return res, fmt.Errorf("Error parsing status code: %w", err)
582			}
583			sr.status = status
584			if f.StreamEnded() {
585				if streamEnded(res, streams, sr) {
586					break loop
587				}
588			}
589		case *http2.PushPromiseFrame:
590			_, err := st.dec.Write(f.HeaderBlockFragment())
591			if err != nil {
592				return res, err
593			}
594			sr := &serverResponse{
595				streamID:  f.PromiseID,
596				reqHeader: cloneHeader(st.header),
597			}
598			streams[sr.streamID] = sr
599		case *http2.DataFrame:
600			sr, ok := streams[f.FrameHeader.StreamID]
601			if !ok {
602				break
603			}
604			sr.body = append(sr.body, f.Data()...)
605			if f.StreamEnded() {
606				if streamEnded(res, streams, sr) {
607					break loop
608				}
609			}
610		case *http2.RSTStreamFrame:
611			sr, ok := streams[f.FrameHeader.StreamID]
612			if !ok {
613				break
614			}
615			sr.errCode = f.ErrCode
616			if streamEnded(res, streams, sr) {
617				break loop
618			}
619		case *http2.GoAwayFrame:
620			if f.ErrCode == http2.ErrCodeNo {
621				break
622			}
623			res.errCode = f.ErrCode
624			res.connErr = true
625			break loop
626		case *http2.SettingsFrame:
627			if f.IsAck() {
628				break
629			}
630			if err := st.fr.WriteSettingsAck(); err != nil {
631				return res, err
632			}
633		}
634	}
635	sort.Sort(ByStreamID(res.pushResponse))
636	return res, nil
637}
638
639func streamEnded(mainSr *serverResponse, streams map[uint32]*serverResponse, sr *serverResponse) bool {
640	delete(streams, sr.streamID)
641	if mainSr.streamID != sr.streamID {
642		mainSr.pushResponse = append(mainSr.pushResponse, sr)
643	}
644	return len(streams) == 0
645}
646
647type serverResponse struct {
648	status       int               // HTTP status code
649	header       http.Header       // response header fields
650	body         []byte            // response body
651	streamID     uint32            // stream ID in HTTP/2
652	errCode      http2.ErrCode     // error code received in HTTP/2 RST_STREAM or GOAWAY
653	connErr      bool              // true if HTTP/2 connection error
654	connClose    bool              // Connection: close is included in response header in HTTP/1 test
655	reqHeader    http.Header       // http request header, currently only sotres pushed request header
656	pushResponse []*serverResponse // pushed response
657}
658
659type ByStreamID []*serverResponse
660
661func (b ByStreamID) Len() int {
662	return len(b)
663}
664
665func (b ByStreamID) Swap(i, j int) {
666	b[i], b[j] = b[j], b[i]
667}
668
669func (b ByStreamID) Less(i, j int) bool {
670	return b[i].streamID < b[j].streamID
671}
672
673func cloneHeader(h http.Header) http.Header {
674	h2 := make(http.Header, len(h))
675	for k, vv := range h {
676		vv2 := make([]string, len(vv))
677		copy(vv2, vv)
678		h2[k] = vv2
679	}
680	return h2
681}
682
683func noopHandler(w http.ResponseWriter, r *http.Request) {
684	if _, err := io.ReadAll(r.Body); err != nil {
685		http.Error(w, fmt.Sprintf("Error io.ReadAll() = %v", err), http.StatusInternalServerError)
686	}
687}
688
689type APIResponse struct {
690	Status string                 `json:"status,omitempty"`
691	Code   int                    `json:"code,omitempty"`
692	Data   map[string]interface{} `json:"data,omitempty"`
693}
694
695type proxyProtocolV2 struct {
696	command            proxyProtocolV2Command
697	sourceAddress      net.Addr
698	destinationAddress net.Addr
699	additionalData     []byte
700}
701
702type proxyProtocolV2Command int
703
704const (
705	proxyProtocolV2CommandLocal proxyProtocolV2Command = 0x0
706	proxyProtocolV2CommandProxy proxyProtocolV2Command = 0x1
707)
708
709type proxyProtocolV2Family int
710
711const (
712	proxyProtocolV2FamilyUnspec proxyProtocolV2Family = 0x0
713	proxyProtocolV2FamilyInet   proxyProtocolV2Family = 0x1
714	proxyProtocolV2FamilyInet6  proxyProtocolV2Family = 0x2
715	proxyProtocolV2FamilyUnix   proxyProtocolV2Family = 0x3
716)
717
718type proxyProtocolV2Protocol int
719
720const (
721	proxyProtocolV2ProtocolUnspec proxyProtocolV2Protocol = 0x0
722	proxyProtocolV2ProtocolStream proxyProtocolV2Protocol = 0x1
723	proxyProtocolV2ProtocolDgram  proxyProtocolV2Protocol = 0x2
724)
725
726func writeProxyProtocolV2(w io.Writer, hdr proxyProtocolV2) error {
727	if _, err := w.Write([]byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}); err != nil {
728		return err
729	}
730	if _, err := w.Write([]byte{byte(0x20 | hdr.command)}); err != nil {
731		return err
732	}
733
734	switch srcAddr := hdr.sourceAddress.(type) {
735	case *net.TCPAddr:
736		dstAddr := hdr.destinationAddress.(*net.TCPAddr)
737		if len(srcAddr.IP) != len(dstAddr.IP) {
738			panic("len(srcAddr.IP) != len(dstAddr.IP)")
739		}
740		var fam byte
741		if len(srcAddr.IP) == 4 {
742			fam = byte(proxyProtocolV2FamilyInet << 4)
743		} else {
744			fam = byte(proxyProtocolV2FamilyInet6 << 4)
745		}
746		fam |= byte(proxyProtocolV2ProtocolStream)
747		if _, err := w.Write([]byte{fam}); err != nil {
748			return err
749		}
750		length := uint16(len(srcAddr.IP)*2 + 4 + len(hdr.additionalData))
751		if err := binary.Write(w, binary.BigEndian, length); err != nil {
752			return err
753		}
754		if _, err := w.Write(srcAddr.IP); err != nil {
755			return err
756		}
757		if _, err := w.Write(dstAddr.IP); err != nil {
758			return err
759		}
760		if err := binary.Write(w, binary.BigEndian, uint16(srcAddr.Port)); err != nil {
761			return err
762		}
763		if err := binary.Write(w, binary.BigEndian, uint16(dstAddr.Port)); err != nil {
764			return err
765		}
766	case *net.UnixAddr:
767		dstAddr := hdr.destinationAddress.(*net.UnixAddr)
768		if len(srcAddr.Name) > 108 {
769			panic("too long Unix source address")
770		}
771		if len(dstAddr.Name) > 108 {
772			panic("too long Unix destination address")
773		}
774		fam := byte(proxyProtocolV2FamilyUnix << 4)
775		switch srcAddr.Net {
776		case "unix":
777			fam |= byte(proxyProtocolV2ProtocolStream)
778		case "unixdgram":
779			fam |= byte(proxyProtocolV2ProtocolDgram)
780		default:
781			fam |= byte(proxyProtocolV2ProtocolUnspec)
782		}
783		if _, err := w.Write([]byte{fam}); err != nil {
784			return err
785		}
786		length := uint16(216 + len(hdr.additionalData))
787		if err := binary.Write(w, binary.BigEndian, length); err != nil {
788			return err
789		}
790		zeros := make([]byte, 108)
791		if _, err := w.Write([]byte(srcAddr.Name)); err != nil {
792			return err
793		}
794		if _, err := w.Write(zeros[:108-len(srcAddr.Name)]); err != nil {
795			return err
796		}
797		if _, err := w.Write([]byte(dstAddr.Name)); err != nil {
798			return err
799		}
800		if _, err := w.Write(zeros[:108-len(dstAddr.Name)]); err != nil {
801			return err
802		}
803	default:
804		fam := byte(proxyProtocolV2FamilyUnspec<<4) | byte(proxyProtocolV2ProtocolUnspec)
805		if _, err := w.Write([]byte{fam}); err != nil {
806			return err
807		}
808		length := uint16(len(hdr.additionalData))
809		if err := binary.Write(w, binary.BigEndian, length); err != nil {
810			return err
811		}
812	}
813
814	if _, err := w.Write(hdr.additionalData); err != nil {
815		return err
816	}
817
818	return nil
819}
820