Understanding the TCP protocol used for communication between PostgreSQL server and client
Startup Phase
The basic flow of the startup phase is as follows:
Client sends a StartupMessage (F) to initiate connection request to server
Payload includes 0x30000 Int32 version number magic, and a series of kv-structured runtime parameters (NULL0 separated, required parameter is user),
Client waits for server response, mainly waiting for ReadyForQuery (Z) event sent by server, which represents that server is ready to receive requests.
The above are the two main events in connection establishment process. Other events include authentication messages AuthenticationXXX (R), backend key messages BackendKeyData (K), error messages ErrorResponse (E), and a series of context-independent messages (NoticeResponse (N), NotificationResponse (A), ParameterStatus(S))
We can write a Go program to simulate this process:
Based on jackc/pgx/pgproto3, you can easily write some middleware. For example, the following code is a very simple “connection proxy”:
packagemainimport("io""net""strings""time""github.com/jackc/pgx/pgproto3")typeProxyServerstruct{UpstreamAddrstringListenAddrstringListenernet.ListenerDialernet.Dialer}funcNewProxyServer(listenAddr,upstreamAddrstring)*ProxyServer{ln,_:=net.Listen(`tcp4`,listenAddr)return&ProxyServer{ListenAddr:listenAddr,UpstreamAddr:upstreamAddr,Listener:ln,Dialer:net.Dialer{KeepAlive:1*time.Minute},}}func(ps*ProxyServer)Serve()error{for{conn,err:=ps.Listener.Accept()iferr!=nil{panic(err)}gops.ServeOne(conn)}}func(ps*ProxyServer)ServeOne(clientConnnet.Conn)error{backend,_:=pgproto3.NewBackend(clientConn,clientConn)startupMsg,err:=backend.ReceiveStartupMessage()iferr!=nil&&strings.Contains(err.Error(),"ssl"){if_,err:=clientConn.Write([]byte(`N`));err!=nil{panic(err)}// ssl is not welcome, now receive real startup msgstartupMsg,err=backend.ReceiveStartupMessage()iferr!=nil{panic(err)}}serverConn,_:=ps.Dialer.Dial(`tcp4`,ps.UpstreamAddr)frontend,_:=pgproto3.NewFrontend(serverConn,serverConn)frontend.Send(startupMsg)errChan:=make(chanerror,2)gofunc(){_,err:=io.Copy(clientConn,serverConn)errChan<-err}()gofunc(){_,err:=io.Copy(serverConn,clientConn)errChan<-err}()return<-errChan}funcmain(){proxy:=NewProxyServer("127.0.0.1:5433","127.0.0.1:5432")proxy.Serve()}
Here the proxy listens on port 5433 and parses and forwards messages to the real database server on port 5432. Execute the following command in another session:
$ psql postgres://127.0.0.1:5433/data?sslmode=disable -c 'SELECT * FROM pg_stat_activity LIMIT 1;'
You can observe message exchanges during this process: