diff --git a/README.md b/README.md index 0b5bea9..6f36276 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,12 @@ import "github.com/rvflash/tcp" ## Features +### TLS support + +By using the `RunTLS` method instead of `Run`, you can specify a certificate and +a X509 key to create an TCP/TLS connection. + + ### Handler Just as Gin, a well done web framework whose provides functions based on HTTP methods, diff --git a/server.go b/server.go index d1fb37d..205bbd8 100644 --- a/server.go +++ b/server.go @@ -3,6 +3,7 @@ package tcp import ( "context" + "crypto/tls" "net" "sync" "time" @@ -120,13 +121,33 @@ func (s *Server) Use(f ...HandlerFunc) Router { return s } +const network = "tcp" + // Run starts listening on TCP address. // This method will block the calling goroutine indefinitely unless an error happens. func (s *Server) Run(addr string) (err error) { - l, err := net.Listen("tcp", addr) + l, err := net.Listen(network, addr) + if err != nil { + return + } + return s.serve(l) +} + +// RunTLS acts identically to the Run method, except that it uses the TLS protocol. +// This method will block the calling goroutine indefinitely unless an error happens. +func (s *Server) RunTLS(addr, certFile, keyFile string) (err error) { + c, err := tlsConfig(certFile, keyFile) if err != nil { return } + l, err := tls.Listen(network, addr, c) + if err != nil { + return + } + return s.serve(l) +} + +func (s *Server) serve(l net.Listener) (err error) { defer func() { if err == nil { err = l.Close() @@ -177,6 +198,13 @@ func (s *Server) computeHandlers(segment string) []HandlerFunc { return m } +func tlsConfig(certFile, keyFile string) (*tls.Config, error) { + var err error + c := make([]tls.Certificate, 1) + c[0], err = tls.LoadX509KeyPair(certFile, keyFile) + return &tls.Config{Certificates: c}, err +} + func read(l net.Listener, to time.Duration) (net.Conn, error) { c, err := l.Accept() if err != nil { diff --git a/server_test.go b/server_test.go index 9108850..2a2686b 100644 --- a/server_test.go +++ b/server_test.go @@ -1,6 +1,7 @@ package tcp_test import ( + "crypto/tls" "fmt" "io" "net" @@ -92,14 +93,34 @@ func handleResp(segment string, expected bool) string { } const ( - eol = "\n" - clientAddr = ":9123" - hiMsg = "hi, there's someone?" + eol - receivedMsg = "received: %d bytes" + eol - welcomeMsg = "welcome" + eol + eol = "\n" + clientAddr = ":9123" + clientTLSAddr = ":9443" + hiMsg = "hi, there's someone?" + eol + receivedMsg = "received: %d bytes" + eol + welcomeMsg = "welcome" + eol ) func TestServer_Run(t *testing.T) { + run(t, false) +} + +func TestServer_RunTLS(t *testing.T) { + run(t, true) +} + +func readConn(c io.Reader, size int) (out []byte, err error) { + out = make([]byte, size) + _, err = c.Read(out) + return +} + +const ( + certFile = "./testdata/server.pem" + keyFile = "./testdata/server.key" +) + +func run(t *testing.T, https bool) { // Prepares the server are := is.New(t) srv := tcp.New() @@ -107,13 +128,32 @@ func TestServer_Run(t *testing.T) { srv.SYN(welcome) srv.FIN(bye) go func() { - err := srv.Run(clientAddr) + var err error + if https { + err = srv.RunTLS(clientTLSAddr, "./testdata/server.pem", "./testdata/server.key") + } else { + err = srv.Run(clientAddr) + } are.NoErr(err) }() - time.Sleep(time.Millisecond * 50) + time.Sleep(time.Millisecond * 100) // Initiates the client - cli, err := net.Dial("tcp", clientAddr) + var ( + cert tls.Certificate + cli net.Conn + err error + ) + if https { + cert, err = tls.LoadX509KeyPair(certFile, keyFile) + are.NoErr(err) + cli, err = tls.Dial("tcp", clientTLSAddr, &tls.Config{ + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: true, + }) + } else { + cli, err = net.Dial("tcp", clientAddr) + } are.NoErr(err) defer func() { are.NoErr(cli.Close()) @@ -131,12 +171,6 @@ func TestServer_Run(t *testing.T) { are.Equal(string(out), fmt.Sprintf(receivedMsg, len(hiMsg))) } -func readConn(c io.Reader, size int) (out []byte, err error) { - out = make([]byte, size) - _, err = c.Read(out) - return -} - func writeConn(w io.Writer, data string) (err error) { _, err = w.Write([]byte(data)) return diff --git a/testdata/server.key b/testdata/server.key new file mode 100644 index 0000000..35a024a --- /dev/null +++ b/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAs4UV55Cg8cRnDwCl1GblBsWuxHqfCgmLLa+ASCcoYC0GVYyy +u3YvY7NqRMduCbvh5+Xsspy1/pTyfl5uxDn4fRXf0+zFJ8ZVunG+Nc36awlqlyjW +Al5bsGHut+ALGGZ4E2PsF0b/JOmPR/g9tASK09uGfnxPTcmFesIjYorfuK+AufLz +ZhvOv7l2yjDAKhlTu0kZDC25udwB89pLCdPHHYaaTqfiDWVe5dvq+tnnkaN1b9oD +IfHhvDpqX+lZOnvrFcP6H5bD2OTVC+1EgqZMi5tcLZZa8Lux3FaCZMKjFvV7PUvC +t2hp2fRvvwH9cCpHRcPvZocRYw/xQuZeRygmewIDAQABAoIBAFq27Ku4VOv24FEf +dazQVflHGZplkEpFKlRqs/tB9lArUGEYmRTLkwThm9inoj4+99f0Qti7Aehkoekj +lPuJg7zJufJgWfxQZ++wHHsZ2+oMtxUtaH+H6PNHeZbnGxQ4a4L44kuc779aWrH2 +ik+WYATQkLUH4hcft1iiE7lvDcBH7a2JNQ3mKWTZRQswQfCEUY++sjwcKf1DQNt0 +4lfZH0yxf+wgdqOszDM0uzRKWG9/u0Ks1IEiDOudQ/XHagHeyvNUqDnVY8bHMdPl +87Hzj4zcmEd7bvt2H/Wo9AYiVqaBb+mpt+Rd4AKTNKaztH5fp3vdniWou564q+HC +eLyeqvECgYEA7ApSCHN/JnOviyJAr/08U59xQXVSHYnHm44XRF9mOyllsdcZYq66 +bP9UwfeznQqxGjbLhMl/IfT2lvc2NEojGK2snTW0wL37usN4OzMEK63BYFJZIWtO +lhi21y6vvzLkVNpuuxaZcbbVuxLZ164r3wxeLWTU3Nndp+mCyCXj1TMCgYEAwrM9 +TWAH41cInW5OmCyBdYuE1fO1pX/WcxHKfrOtZZEpJsJ1f/6vxP70sYrZM0Sst5Rt +L8yuuCi1w5m2wYTaT7Q2mKQNLi8eEKp+LTP3Dk2e8op7+3rL4IePm6Je4xyBGFMB +AmrYkTbYfapScA8/eRpWPkhL8smOOXfT4vGlWZkCgYEAzDHVa5g23BXiO9QC7/x7 +zr+1I3KR+hmyQ2C1wiFheDbE0geJ33PWtOph8XcOvAF+2nWitJn30HFxTYJJMw0b +tYKxPEM0vjTca2IkRdpJdlvXWTOEBcQl9PUmX5r00Mv++WUJu/G+BsCrvy18pPf4 +3fl+E3kSKIiVF2iMCge1RD8CgYBGJQMx7CYLXqDI7yO7qVUevxwlZL8NmAfvvvog +OaV/0RNtVmO0sFIs+9m2WRq1YMmE/iF0Nh7AVFJf3PI9uBpa2sdMqNXizLDdOj9p +E66vZYGdSg0u5eerxcsUPCRJWEyeMdFMSKgrcQIADCvVGXk5rSZjG/LYzorB44zf +10VGCQKBgBEAdGw6J9jNeaGpmGbY3HfMJnbahujUK57dS8SwTDwh1jWX+mWhb2WU +oLn+JFjj/QZbf9FqsUIXF6SRwF6r8xICTsvBHoSlYbAtkITJLn5yffnsyBV3RCCc +QqQ6UoOmiYOboMJZPsrf//LGNr9LwWFFEXmVwGwlu6zBv4UAJP49 +-----END RSA PRIVATE KEY----- diff --git a/testdata/server.pem b/testdata/server.pem new file mode 100644 index 0000000..c1cd18f --- /dev/null +++ b/testdata/server.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID7jCCAtagAwIBAgIJAMMTTMF9mCNYMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD +VQQGEwJGUjEOMAwGA1UECAwFUGFyaXMxDjAMBgNVBAcMBVBhcmlzMRAwDgYDVQQK +DAdSdkZsYXNoMRkwFwYDVQQDDBBIZXJ2w4PCqSBHb3VjaGV0MS8wLQYJKoZIhvcN +AQkBFiBydmZsYXNoQHVzZXJzLm5vcmVwbHkuZ2l0aHViLmNvbTAeFw0xOTAzMjcy +MTA0MDNaFw0yMDAzMjYyMTA0MDNaMIGLMQswCQYDVQQGEwJGUjEOMAwGA1UECAwF +UGFyaXMxDjAMBgNVBAcMBVBhcmlzMRAwDgYDVQQKDAdSdkZsYXNoMRkwFwYDVQQD +DBBIZXJ2w4PCqSBHb3VjaGV0MS8wLQYJKoZIhvcNAQkBFiBydmZsYXNoQHVzZXJz +Lm5vcmVwbHkuZ2l0aHViLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALOFFeeQoPHEZw8ApdRm5QbFrsR6nwoJiy2vgEgnKGAtBlWMsrt2L2OzakTH +bgm74efl7LKctf6U8n5ebsQ5+H0V39PsxSfGVbpxvjXN+msJapco1gJeW7Bh7rfg +CxhmeBNj7BdG/yTpj0f4PbQEitPbhn58T03JhXrCI2KK37ivgLny82Ybzr+5dsow +wCoZU7tJGQwtubncAfPaSwnTxx2Gmk6n4g1lXuXb6vrZ55GjdW/aAyHx4bw6al/p +WTp76xXD+h+Ww9jk1QvtRIKmTIubXC2WWvC7sdxWgmTCoxb1ez1Lwrdoadn0b78B +/XAqR0XD72aHEWMP8ULmXkcoJnsCAwEAAaNTMFEwHQYDVR0OBBYEFCNCzkBAOykC +W6RPMLNbnRH70GgvMB8GA1UdIwQYMBaAFCNCzkBAOykCW6RPMLNbnRH70GgvMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALItf6DLt8tFM0hKgTHk +athZ7uRpzPXrucKxJa6y5HSh04i+Lxf+czGpF1Y7WavZlPj3nNTQuJ4mTxTX6Jcx +IvC9kQwFBT7m21kGaobmqp1VzND0EHZ1ZdL+eNCUCkCUmDbp2uIBBQebzRNCqr9c +e0qZz7cHZYGeE1gOgzEwQ6BnmDaF8DOtr5vMogGioNCgDxYAOpR2cZ2Kjzb1jsTX +SfLONDPVuuDwqKmZyMU/eudsd9zyKDnfUDT280gT0GyJO0Z9pZNaVW+xc98Z5mWM +i8oXXFRv2nUJOs8uBVTOUfbNhmaTx1eRXDYCgZAtvFHpq7QTd75m+oUWtlA+JyEw +qtU= +-----END CERTIFICATE-----