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
|
|
|
}
|
|
|
|
|
2024-08-20 16:24:45 +00:00
|
|
|
var (
|
|
|
|
tlsHeaderLength = 5
|
|
|
|
tlsMaxMessageLength = 16384
|
|
|
|
)
|
|
|
|
|
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-08-20 16:24:45 +00:00
|
|
|
// 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, tlsHeaderLength+tlsMaxMessageLength)
|
|
|
|
|
|
|
|
length, err := io.ReadAtLeast(conn, data, tlsHeaderLength)
|
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
|
|
|
|
}
|
|
|
|
|
2024-08-20 16:24:45 +00:00
|
|
|
// otherwise, ReadAtLeast may produce an index out of bounds
|
|
|
|
clientHelloLength := min(int(data[3])<<8+int(data[4]), tlsMaxMessageLength)
|
|
|
|
|
|
|
|
bLength, err := io.ReadAtLeast(conn, data[length:], tlsHeaderLength+clientHelloLength-length)
|
|
|
|
if err != nil {
|
|
|
|
klog.V(4).ErrorS(err, fmt.Sprintf("Error reading ClientHello of length %d from the connection", clientHelloLength))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
length += bLength
|
|
|
|
|
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 {
|
2018-12-05 05:17:17 +00:00
|
|
|
klog.Errorf("Error writing the first 4k of proxy data: %v", 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
|
|
|
}
|