netpoll
netpoll copied to clipboard
TLS
Is TLS available with netpoll?
@da-tai not yet, but is developing.
@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
}
@joway Is there any progress in this work and when is netpolll expected to support tls?
@loveyana there is no specific time. but you also can use "crypto/tls" lib with netpoll.Connection what @lazychanger has listed below.
@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.
@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?
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.
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")
}
})
}
请问这个有最新进展吗?
@ym2049 It should be refactor totally to support TLS with an elegant way. Now still have no time to do this.