netpoll icon indicating copy to clipboard operation
netpoll copied to clipboard

TLS

Open da-tai opened this issue 4 years ago • 7 comments
trafficstars

Is TLS available with netpoll?

da-tai avatar Oct 17 '21 21:10 da-tai

@da-tai not yet, but is developing.

joway avatar Oct 18 '21 08:10 joway

@da-tai

package main

import (
	"context"
	"crypto/tls"
	"flag"
	"github.com/cloudwego/netpoll"
	"log"
	"net"
)

var cert tls.Certificate

func init() {
	var (
		cer string
		key string
		err error
	)

	flag.StringVar(&cer, "cert", "", "cer")
	flag.StringVar(&key, "key", "", "pri")
	flag.Parse()

	cert, err = tls.LoadX509KeyPair(cer, key)
	if err != nil {
		log.Panicln(err)
	}
}

func main() {
	el, _ := netpoll.NewEventLoop(onRequest)

	l, _ := net.Listen("tcp", ":9001")

	if err := el.Serve(l); err != nil {
		log.Println(err)
	}
}

func onRequest(ctx context.Context, connection netpoll.Connection) error {
	tlsConn := tls.Server(connection, &tls.Config{
		Certificates: []tls.Certificate{
			cert,
		},
	})

	if err := tlsConn.Handshake(); err != nil {
		// handshake failed
		log.Panicln(err)
		return err
	}

	// handshake ok
	// todo continue onRequest

	return nil
}

lazychanger avatar Jan 28 '22 03:01 lazychanger

@joway Is there any progress in this work and when is netpolll expected to support tls?

loveyana avatar Feb 17 '22 06:02 loveyana

@loveyana there is no specific time. but you also can use "crypto/tls" lib with netpoll.Connection what @lazychanger has listed below.

joway avatar Feb 17 '22 06:02 joway

@loveyana there is no specific time. but you also can use "crypto/tls" lib with netpoll.Connection what @lazychanger has listed below.

Ok, it looks great, but there is no way to use TLS in this way in the KiteX, because it depends on Netpoll, so I am looking forward to this feature, thank you for your reply.

loveyana avatar Feb 17 '22 07:02 loveyana

@da-tai

package main

import (
	"context"
	"crypto/tls"
	"flag"
	"github.com/cloudwego/netpoll"
	"log"
	"net"
)

var cert tls.Certificate

func init() {
	var (
		cer string
		key string
		err error
	)

	flag.StringVar(&cer, "cert", "", "cer")
	flag.StringVar(&key, "key", "", "pri")
	flag.Parse()

	cert, err = tls.LoadX509KeyPair(cer, key)
	if err != nil {
		log.Panicln(err)
	}
}

func main() {
	el, _ := netpoll.NewEventLoop(onRequest)

	l, _ := net.Listen("tcp", ":9001")

	if err := el.Serve(l); err != nil {
		log.Println(err)
	}
}

func onRequest(ctx context.Context, connection netpoll.Connection) error {
	tlsConn := tls.Server(connection, &tls.Config{
		Certificates: []tls.Certificate{
			cert,
		},
	})

	if err := tlsConn.Handshake(); err != nil {
		// handshake failed
		log.Panicln(err)
		return err
	}

	// handshake ok
	// todo continue onRequest

	return nil
}

Testing this. When using reader := connection.Reader() reader.ReadBinary(reader.Len()), I get no data when OnRequest is called. Should I be usingtlsConn.Read() ? Do you have an example of this?

da-tai avatar Aug 05 '22 01:08 da-tai

server.go

package main

import (
	"context"
	"crypto/tls"
	"flag"
	"github.com/cloudwego/netpoll"
	"io"
	logger "log"
	"net"
	"os"
	"time"
)

const tlsConnCtxKey = "TLS_CONN"

var (
	conf    *tls.Config
	bufSize = 64
	log     = logger.New(os.Stderr, "[server] ", logger.LstdFlags|logger.Lmsgprefix)
)

func init() {
	var (
		cer string
		key string
	)

	flag.StringVar(&cer, "cert", "runtime/public.crt", "cer")
	flag.StringVar(&key, "key", "runtime/privatekey.pem", "pri")
	flag.IntVar(&bufSize, "size", 64, "buf size")
	flag.Parse()

	cert, err := tls.LoadX509KeyPair(cer, key)
	if err != nil {
		log.Panicln(err)
	}

	conf = &tls.Config{
		Certificates: []tls.Certificate{
			cert,
		},
	}
}

func main() {

	el, _ := netpoll.NewEventLoop(onRequest, netpoll.WithOnConnect(onConnection), netpoll.WithReadTimeout(time.Second*10),
		netpoll.WithIdleTimeout(time.Second*60))

	l, err := net.Listen("tcp", ":9001")
	if err != nil {
		log.Panicln(err)
	}
	if err := el.Serve(l); err != nil {
		log.Println(err)
	}
}

func onConnection(ctx context.Context, conn netpoll.Connection) context.Context {
	tlsConn := tls.Server(conn, conf)

	log.Println("connected")
	log.Println(tlsConn.Write([]byte("pong")))
	return context.WithValue(ctx, tlsConnCtxKey, tlsConn)
}

func onRequest(ctx context.Context, _ netpoll.Connection) error {
	tlsConn := ctx.Value(tlsConnCtxKey).(*tls.Conn)

	buf := make([]byte, bufSize)
	content := make([]byte, 0)
	for {
		size, err := tlsConn.Read(buf)
		if err != nil {
			if err != io.EOF {
				log.Println(err)
			}
			goto FLUSH
		} else {
			content = append(content, buf[0:size]...)
		}

	FLUSH:
		// flush
		if size < bufSize || err != nil {
			log.Printf("flush %s", string(content))
			content = make([]byte, 0)
			return nil
		}
	}
}

client.go

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"flag"
	"io"
	logger "log"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"
)

var (
	conf *tls.Config
	log  = logger.New(os.Stderr, "[client] ", logger.LstdFlags|logger.Lmsgprefix)
)

func init() {
	var (
		cer string
		key string
	)

	flag.StringVar(&cer, "cert", "runtime/public.crt", "cer")
	flag.StringVar(&key, "key", "runtime/privatekey.pem", "pri")
	flag.Parse()

	cert, err := tls.LoadX509KeyPair(cer, key)
	if err != nil {
		log.Panicln(err)
	}

	x509CertInfo, err := x509.ParseCertificate(cert.Certificate[0])
	if err != nil {
		log.Panicln(err)
	}

	conf = &tls.Config{
		Certificates: []tls.Certificate{
			cert,
		},
		ServerName: strings.ReplaceAll(x509CertInfo.DNSNames[0], "*", "demo"),
	}
}

func main() {
	wg := &sync.WaitGroup{}
	sig := make(chan os.Signal, 1)
	done := make(chan struct{}, 1)
	signal.Notify(sig, syscall.SIGKILL, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)

	ctx, cancel := context.WithCancel(context.Background())

	for i := 0; i < 10; i++ {
		go NewDialer(ctx, wg)
	}

	go func() {
		wg.Wait()
		done <- struct{}{}
	}()

	select {
	case <-sig:
		cancel()
		<-done
		break
	case <-done:
		break

	}
}

func NewDialer(ctx context.Context, wg *sync.WaitGroup) {
	wg.Add(1)

	subctx, subCancel := context.WithCancel(ctx)
	defer func() {
		subCancel()
		wg.Done()

	}()
	conn, err := tls.Dial("tcp", "127.0.0.1:9001", conf)
	//conn, err := netpoll.DialConnection("tcp", "127.0.0.1:9001", time.Second*10)

	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close()

	pingTicker := time.NewTicker(time.Second)
	defer pingTicker.Stop()

	go func() {
		for range pingTicker.C {
			_, _ = conn.Write([]byte("ping"))
		}
	}()

	done := make(chan error, 1)

	go func() {
		var (
			tlsConn = conn
			// tlsConn = tls.Server(conn, conf)
			bufSize = 64
			buf     = make([]byte, bufSize)
			content = make([]byte, 0)
			err     error
			size    int
		)

		for {
			size, err = tlsConn.Read(buf)
			if err != nil {
				if err != io.EOF {
					log.Println(err)
				}
				goto FLUSH
			} else {
				content = append(content, buf[0:size]...)
			}

		FLUSH:
			// flush
			if size < bufSize {
				log.Printf("flush %s", string(content))
				content = make([]byte, 0)
			}
			if err != nil {
				done <- err
				return
			}
		}
	}()

	select {
	case <-subctx.Done():
		log.Println("client close")
		break
	case err := <-done:
		if err != nil {
			if err == io.EOF {
				log.Printf("server has been closed")
			} else {
				log.Printf("closed: %s", err)
			}
		}
		break
	}
}

@da-tai I am tried netpoll.DialConnection(), but it alway timeout on handshake. So i used tls.Dial(). You can try it.

lazychanger avatar Aug 05 '22 08:08 lazychanger

I could establish a tls client connection with netpoll however... ~~don't know what's the blocker here~~

package main

import (
	"crypto/tls"
	"fmt"
	"time"

	"github.com/cloudwego/netpoll"
)

func main() {
	conn, err := netpoll.DialConnection("tcp", "www.baidu.com:443", time.Minute*1)
	if err != nil {
		return
	}
	cli := tls.Client(conn, &tls.Config{
		ServerName: "www.baidu.com",
	})
	_ = cli.Handshake()
	cli.Write([]byte("GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n"))
	buf := make([]byte, 1024)
	_, _ = cli.Read(buf)
	fmt.Println(string(buf))
}

after a shabby benchmark, it looks that when it comes to tls, standard library should be preferred agains netpoll compat api. netpoll couldn't unleash it's advantages under this circumstance.

guess that we have to wait for netpoll to support tls after all

test results
➜  certs go test -benchmem -run=^$ -bench ^BenchmarkNet$ test -count 10
goos: linux
goarch: amd64
pkg: test
cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
BenchmarkNet-8              2493            447195 ns/op           33960 B/op        385 allocs/op
BenchmarkNet-8              2536            443564 ns/op           34000 B/op        386 allocs/op
BenchmarkNet-8              2715            436020 ns/op           33977 B/op        386 allocs/op
BenchmarkNet-8              2778            435836 ns/op           33988 B/op        386 allocs/op
BenchmarkNet-8              2874            457998 ns/op           33988 B/op        386 allocs/op
BenchmarkNet-8              2494            440287 ns/op           34019 B/op        387 allocs/op
BenchmarkNet-8              2504            443286 ns/op           33945 B/op        385 allocs/op
BenchmarkNet-8              2842            430595 ns/op           33990 B/op        386 allocs/op
BenchmarkNet-8              2654            441614 ns/op           34000 B/op        386 allocs/op
BenchmarkNet-8              2682            437373 ns/op           33995 B/op        386 allocs/op
PASS
ok      test    13.253s
➜  certs go test -benchmem -run=^$ -bench ^BenchmarkNetpoll$ test -count 10
goos: linux
goarch: amd64
pkg: test
cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
BenchmarkNetpoll-8          2390            478605 ns/op           49087 B/op        408 allocs/op
BenchmarkNetpoll-8          2541            479114 ns/op           49083 B/op        409 allocs/op
BenchmarkNetpoll-8          2152            500161 ns/op           49095 B/op        409 allocs/op
BenchmarkNetpoll-8          2352            478208 ns/op           49107 B/op        409 allocs/op
BenchmarkNetpoll-8          2154            473435 ns/op           49072 B/op        409 allocs/op
BenchmarkNetpoll-8          2403            467416 ns/op           49133 B/op        409 allocs/op
BenchmarkNetpoll-8          2424            453431 ns/op           49163 B/op        410 allocs/op
BenchmarkNetpoll-8          2500            452993 ns/op           49096 B/op        410 allocs/op
BenchmarkNetpoll-8          2414            455957 ns/op           49220 B/op        410 allocs/op
BenchmarkNetpoll-8          2508            476798 ns/op           49073 B/op        409 allocs/op
PASS
ok      test    13.951s
➜  certs go test -benchmem -benchtime 5s -run=^$ -bench ^BenchmarkNet$ test -count 10 
goos: linux
goarch: amd64
pkg: test
cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
BenchmarkNet-8             13502            440468 ns/op           34000 B/op        386 allocs/op
BenchmarkNet-8             13935            450034 ns/op           33984 B/op        386 allocs/op
BenchmarkNet-8             13110            449011 ns/op           33987 B/op        386 allocs/op
BenchmarkNet-8             13672            452542 ns/op           33989 B/op        386 allocs/op
BenchmarkNet-8             13665            448629 ns/op           33985 B/op        386 allocs/op
BenchmarkNet-8             13519            453262 ns/op           33990 B/op        386 allocs/op
BenchmarkNet-8             13466            434147 ns/op           34003 B/op        387 allocs/op
BenchmarkNet-8             13418            449372 ns/op           33987 B/op        386 allocs/op
BenchmarkNet-8             13287            445137 ns/op           33993 B/op        386 allocs/op
BenchmarkNet-8             13480            442852 ns/op           34002 B/op        386 allocs/op
PASS
ok      test    105.337s
➜  certs go test -benchmem -benchtime 5s -run=^$ -bench ^BenchmarkNetpoll$ test -count 10
goos: linux
goarch: amd64
pkg: test
cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
BenchmarkNetpoll-8         12618            473713 ns/op           49107 B/op        409 allocs/op
BenchmarkNetpoll-8         10000            876873 ns/op           49189 B/op        411 allocs/op
BenchmarkNetpoll-8          3994           1389352 ns/op           49165 B/op        412 allocs/op
BenchmarkNetpoll-8          4508           1455530 ns/op           49241 B/op        411 allocs/op
BenchmarkNetpoll-8          3657           1468434 ns/op           49196 B/op        412 allocs/op
BenchmarkNetpoll-8          4141           1463762 ns/op           49160 B/op        411 allocs/op
BenchmarkNetpoll-8          3658           3349753 ns/op           49346 B/op        416 allocs/op
BenchmarkNetpoll-8           853           7249948 ns/op           49831 B/op        426 allocs/op
BenchmarkNetpoll-8           780           7111392 ns/op           49844 B/op        427 allocs/op
BenchmarkNetpoll-8           859           6653704 ns/op           49724 B/op        424 allocs/op
PASS
ok      test    84.839s
test code
func Request(url *url.URL) string {
    conn, _ := netpoll.DialConnection("tcp", url.Host, time.Minute*1)
    cli := tls.Client(conn, &tls.Config{
        ServerName:         url.Hostname(),
        InsecureSkipVerify: true,
    })
    _ = cli.Handshake()
    cli.Write([]byte(fmt.Sprintf("GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", url.Hostname())))
    buf := make([]byte, 1024)
    result := ""
    for {
        s, err := cli.Read(buf)
        result += string(buf[:s])
        if err != nil || s == 0 {
            break
        }
    }
    return result
}

func RequestRaw(url *url.URL) string {
    conn, _ := net.Dial("tcp", url.Host)
    cli := tls.Client(conn, &tls.Config{
        ServerName:         url.Hostname(),
        InsecureSkipVerify: true,
    })
    _ = cli.Handshake()
    cli.Write([]byte(fmt.Sprintf("GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", url.Hostname())))
    buf := make([]byte, 1024)
    result := ""
    for {
        s, err := cli.Read(buf)
        result += string(buf[:s])
        if err != nil || s == 0 {
            break
        }
    }
    return result
}

func BenchmarkNetpoll(b *testing.B) {
    u, _ := url.Parse("https://localhost:1443")
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            assert.Equal(b, strings.Split(Request(u), "\r\n\r\n")[1], "ok")
        }
    })
}

func BenchmarkNet(b *testing.B) {
    u, _ := url.Parse("https://localhost:1443")
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            assert.Equal(b, strings.Split(RequestRaw(u), "\r\n\r\n")[1], "ok")
        }
    })
}

frankli0324 avatar Feb 02 '23 06:02 frankli0324

请问这个有最新进展吗?

ym2049 avatar May 09 '23 03:05 ym2049

@ym2049 It should be refactor totally to support TLS with an elegant way. Now still have no time to do this.

joway avatar May 09 '23 05:05 joway