2017-11-05 21:35:46 +00:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2022-07-20 21:43:39 +00:00
package tcpproxy
2017-04-09 23:51:38 +00:00
import (
"fmt"
"io"
"net"
2020-08-08 23:31:02 +00:00
"k8s.io/klog/v2"
2017-09-17 18:42:31 +00:00
2020-02-07 15:27:43 +00:00
"pault.ag/go/sniff/parser"
2017-04-09 23:51:38 +00:00
)
2018-06-13 18:15:45 +00:00
// TCPServer describes a server that works in passthrough mode.
2017-11-05 01:18:28 +00:00
type TCPServer struct {
2017-05-11 18:04:19 +00:00
Hostname string
IP string
Port int
2017-04-28 22:21:47 +00:00
ProxyProtocol bool
2017-04-09 23:51:38 +00:00
}
2018-06-13 18:15:45 +00:00
// TCPProxy describes the passthrough servers and a default as catch all.
2017-11-05 01:18:28 +00:00
type TCPProxy struct {
ServerList [ ] * TCPServer
Default * TCPServer
2017-04-09 23:51:38 +00:00
}
2018-06-13 18:15:45 +00:00
// Get returns the TCPServer to use for a given host.
2017-11-05 01:18:28 +00:00
func ( p * TCPProxy ) Get ( host string ) * TCPServer {
2017-05-26 18:25:06 +00:00
if p . ServerList == nil {
return p . Default
}
2017-04-09 23:51:38 +00:00
for _ , s := range p . ServerList {
if s . Hostname == host {
return s
}
}
2017-05-08 21:44:43 +00:00
return p . Default
2017-04-09 23:51:38 +00:00
}
2017-12-02 15:02:00 +00:00
// Handle reads enough information from the connection to extract the hostname
// and open a connection to the passthrough server.
2017-11-05 01:18:28 +00:00
func ( p * TCPProxy ) Handle ( conn net . Conn ) {
2017-04-09 23:51:38 +00:00
defer conn . Close ( )
2024-10-25 22:49:59 +00:00
// [Documentation by @maxl99](https://github.com/kubernetes/ingress-nginx/pull/11843/files#diff-aef3e187fd37c68706ad582d7b89a2d9ad11691bd929a2158b86f93362244105R67-R79)
// It appears that the ClientHello must fit into *one* TLSPlaintext message:
// When a client first connects to a server, it is REQUIRED to send the ClientHello as its first TLS message.
// Source: https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2
//
// length: The length (in bytes) of the following TLSPlaintext.fragment. The length MUST NOT exceed 2^14 bytes.
// An endpoint that receives a record that exceeds this length MUST terminate the connection with a "record_overflow" alert.
// Source: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1
// bytes 0 : content type
// bytes 1-2: legacy version
// bytes 3-4: length
// bytes 5+ : message
// https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_record
// Thus, we need to allocate 5 + 16384 bytes
data := make ( [ ] byte , parser . TLSHeaderLength + 16384 )
2017-04-09 23:51:38 +00:00
2023-09-28 03:14:24 +00:00
// read the tls header first
_ , err := io . ReadFull ( conn , data [ : parser . TLSHeaderLength ] )
if err != nil {
klog . V ( 4 ) . ErrorS ( err , "Error reading TLS header from the connection" )
return
}
// get the total data length then read the rest
2024-10-25 22:49:59 +00:00
length := min ( int ( data [ 3 ] ) << 8 + int ( data [ 4 ] ) + parser . TLSHeaderLength , len ( data ) )
2023-09-28 03:14:24 +00:00
_ , err = io . ReadFull ( conn , data [ parser . TLSHeaderLength : length ] )
2017-04-09 23:51:38 +00:00
if err != nil {
2023-01-27 15:12:27 +00:00
klog . V ( 4 ) . ErrorS ( err , "Error reading data from the connection" )
2017-04-09 23:51:38 +00:00
return
}
2017-05-11 18:04:19 +00:00
proxy := p . Default
2023-08-31 07:36:48 +00:00
hostname , err := parser . GetHostname ( data )
2017-04-09 23:51:38 +00:00
if err == nil {
2020-09-27 20:32:40 +00:00
klog . V ( 4 ) . InfoS ( "TLS Client Hello" , "host" , hostname )
2017-04-09 23:51:38 +00:00
proxy = p . Get ( hostname )
2017-05-11 18:04:19 +00:00
}
if proxy == nil {
2020-09-27 20:32:40 +00:00
klog . V ( 4 ) . InfoS ( "There is no configured proxy for SSL connections." )
2017-05-11 18:04:19 +00:00
return
2017-04-09 23:51:38 +00:00
}
2020-09-03 02:01:13 +00:00
hostPort := net . JoinHostPort ( proxy . IP , fmt . Sprintf ( "%v" , proxy . Port ) )
2022-12-28 20:59:27 +00:00
klog . V ( 4 ) . InfoS ( "passing to" , "hostport" , hostPort )
2020-09-03 02:01:13 +00:00
clientConn , err := net . Dial ( "tcp" , hostPort )
2017-04-09 23:51:38 +00:00
if err != nil {
2021-08-21 20:42:00 +00:00
klog . V ( 4 ) . ErrorS ( err , "error dialing proxy" , "ip" , proxy . IP , "port" , proxy . Port , "hostname" , proxy . Hostname )
2017-04-09 23:51:38 +00:00
return
}
defer clientConn . Close ( )
2017-04-28 22:21:47 +00:00
if proxy . ProxyProtocol {
2018-06-13 18:15:45 +00:00
// write out the Proxy Protocol header
2023-08-31 07:36:48 +00:00
localAddr , ok := conn . LocalAddr ( ) . ( * net . TCPAddr )
if ! ok {
klog . Errorf ( "unexpected type: %T" , conn . LocalAddr ( ) )
}
remoteAddr , ok := conn . RemoteAddr ( ) . ( * net . TCPAddr )
if ! ok {
klog . Errorf ( "unexpected type: %T" , conn . RemoteAddr ( ) )
}
2017-04-28 22:21:47 +00:00
protocol := "UNKNOWN"
if remoteAddr . IP . To4 ( ) != nil {
protocol = "TCP4"
} else if remoteAddr . IP . To16 ( ) != nil {
protocol = "TCP6"
}
proxyProtocolHeader := fmt . Sprintf ( "PROXY %s %s %s %d %d\r\n" , protocol , remoteAddr . IP . String ( ) , localAddr . IP . String ( ) , remoteAddr . Port , localAddr . Port )
2020-09-27 20:32:40 +00:00
klog . V ( 4 ) . InfoS ( "Writing Proxy Protocol" , "header" , proxyProtocolHeader )
2022-12-28 20:59:27 +00:00
_ , err = fmt . Fprint ( clientConn , proxyProtocolHeader )
2017-04-28 22:21:47 +00:00
}
2017-04-09 23:51:38 +00:00
if err != nil {
2020-09-27 20:32:40 +00:00
klog . ErrorS ( err , "Error writing Proxy Protocol header" )
2017-04-09 23:51:38 +00:00
clientConn . Close ( )
2017-04-28 22:21:47 +00:00
} else {
_ , err = clientConn . Write ( data [ : length ] )
if err != nil {
2023-09-28 03:14:24 +00:00
klog . Errorf ( "Error writing the first %d bytes of proxy data: %v" , length , err )
2017-04-28 22:21:47 +00:00
clientConn . Close ( )
}
2017-04-09 23:51:38 +00:00
}
2017-04-28 22:21:47 +00:00
2017-04-09 23:51:38 +00:00
pipe ( clientConn , conn )
}
func pipe ( client , server net . Conn ) {
doCopy := func ( s , c net . Conn , cancel chan <- bool ) {
2023-11-29 14:33:08 +00:00
//nolint:errcheck // No need to catch these errors
io . Copy ( s , c )
2017-04-09 23:51:38 +00:00
cancel <- true
}
cancel := make ( chan bool , 2 )
go doCopy ( server , client , cancel )
go doCopy ( client , server , cancel )
2022-12-28 20:59:27 +00:00
<- cancel
2017-04-09 23:51:38 +00:00
}