proxysocket.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package userspace
  14. import (
  15. "fmt"
  16. "io"
  17. "net"
  18. "strconv"
  19. "strings"
  20. "sync"
  21. "time"
  22. "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/util/runtime"
  24. "k8s.io/klog"
  25. "k8s.io/kubernetes/pkg/proxy"
  26. )
  27. // Abstraction over TCP/UDP sockets which are proxied.
  28. type ProxySocket interface {
  29. // Addr gets the net.Addr for a ProxySocket.
  30. Addr() net.Addr
  31. // Close stops the ProxySocket from accepting incoming connections.
  32. // Each implementation should comment on the impact of calling Close
  33. // while sessions are active.
  34. Close() error
  35. // ProxyLoop proxies incoming connections for the specified service to the service endpoints.
  36. ProxyLoop(service proxy.ServicePortName, info *ServiceInfo, loadBalancer LoadBalancer)
  37. // ListenPort returns the host port that the ProxySocket is listening on
  38. ListenPort() int
  39. }
  40. func newProxySocket(protocol v1.Protocol, ip net.IP, port int) (ProxySocket, error) {
  41. host := ""
  42. if ip != nil {
  43. host = ip.String()
  44. }
  45. switch strings.ToUpper(string(protocol)) {
  46. case "TCP":
  47. listener, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
  48. if err != nil {
  49. return nil, err
  50. }
  51. return &tcpProxySocket{Listener: listener, port: port}, nil
  52. case "UDP":
  53. addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(host, strconv.Itoa(port)))
  54. if err != nil {
  55. return nil, err
  56. }
  57. conn, err := net.ListenUDP("udp", addr)
  58. if err != nil {
  59. return nil, err
  60. }
  61. return &udpProxySocket{UDPConn: conn, port: port}, nil
  62. case "SCTP":
  63. return nil, fmt.Errorf("SCTP is not supported for user space proxy")
  64. }
  65. return nil, fmt.Errorf("unknown protocol %q", protocol)
  66. }
  67. // How long we wait for a connection to a backend in seconds
  68. var EndpointDialTimeouts = []time.Duration{250 * time.Millisecond, 500 * time.Millisecond, 1 * time.Second, 2 * time.Second}
  69. // tcpProxySocket implements ProxySocket. Close() is implemented by net.Listener. When Close() is called,
  70. // no new connections are allowed but existing connections are left untouched.
  71. type tcpProxySocket struct {
  72. net.Listener
  73. port int
  74. }
  75. func (tcp *tcpProxySocket) ListenPort() int {
  76. return tcp.port
  77. }
  78. // TryConnectEndpoints attempts to connect to the next available endpoint for the given service, cycling
  79. // through until it is able to successfully connect, or it has tried with all timeouts in EndpointDialTimeouts.
  80. func TryConnectEndpoints(service proxy.ServicePortName, srcAddr net.Addr, protocol string, loadBalancer LoadBalancer) (out net.Conn, err error) {
  81. sessionAffinityReset := false
  82. for _, dialTimeout := range EndpointDialTimeouts {
  83. endpoint, err := loadBalancer.NextEndpoint(service, srcAddr, sessionAffinityReset)
  84. if err != nil {
  85. klog.Errorf("Couldn't find an endpoint for %s: %v", service, err)
  86. return nil, err
  87. }
  88. klog.V(3).Infof("Mapped service %q to endpoint %s", service, endpoint)
  89. // TODO: This could spin up a new goroutine to make the outbound connection,
  90. // and keep accepting inbound traffic.
  91. outConn, err := net.DialTimeout(protocol, endpoint, dialTimeout)
  92. if err != nil {
  93. if isTooManyFDsError(err) {
  94. panic("Dial failed: " + err.Error())
  95. }
  96. klog.Errorf("Dial failed: %v", err)
  97. sessionAffinityReset = true
  98. continue
  99. }
  100. return outConn, nil
  101. }
  102. return nil, fmt.Errorf("failed to connect to an endpoint.")
  103. }
  104. func (tcp *tcpProxySocket) ProxyLoop(service proxy.ServicePortName, myInfo *ServiceInfo, loadBalancer LoadBalancer) {
  105. for {
  106. if !myInfo.IsAlive() {
  107. // The service port was closed or replaced.
  108. return
  109. }
  110. // Block until a connection is made.
  111. inConn, err := tcp.Accept()
  112. if err != nil {
  113. if isTooManyFDsError(err) {
  114. panic("Accept failed: " + err.Error())
  115. }
  116. if isClosedError(err) {
  117. return
  118. }
  119. if !myInfo.IsAlive() {
  120. // Then the service port was just closed so the accept failure is to be expected.
  121. return
  122. }
  123. klog.Errorf("Accept failed: %v", err)
  124. continue
  125. }
  126. klog.V(3).Infof("Accepted TCP connection from %v to %v", inConn.RemoteAddr(), inConn.LocalAddr())
  127. outConn, err := TryConnectEndpoints(service, inConn.(*net.TCPConn).RemoteAddr(), "tcp", loadBalancer)
  128. if err != nil {
  129. klog.Errorf("Failed to connect to balancer: %v", err)
  130. inConn.Close()
  131. continue
  132. }
  133. // Spin up an async copy loop.
  134. go ProxyTCP(inConn.(*net.TCPConn), outConn.(*net.TCPConn))
  135. }
  136. }
  137. // ProxyTCP proxies data bi-directionally between in and out.
  138. func ProxyTCP(in, out *net.TCPConn) {
  139. var wg sync.WaitGroup
  140. wg.Add(2)
  141. klog.V(4).Infof("Creating proxy between %v <-> %v <-> %v <-> %v",
  142. in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr())
  143. go copyBytes("from backend", in, out, &wg)
  144. go copyBytes("to backend", out, in, &wg)
  145. wg.Wait()
  146. }
  147. func copyBytes(direction string, dest, src *net.TCPConn, wg *sync.WaitGroup) {
  148. defer wg.Done()
  149. klog.V(4).Infof("Copying %s: %s -> %s", direction, src.RemoteAddr(), dest.RemoteAddr())
  150. n, err := io.Copy(dest, src)
  151. if err != nil {
  152. if !isClosedError(err) {
  153. klog.Errorf("I/O error: %v", err)
  154. }
  155. }
  156. klog.V(4).Infof("Copied %d bytes %s: %s -> %s", n, direction, src.RemoteAddr(), dest.RemoteAddr())
  157. dest.Close()
  158. src.Close()
  159. }
  160. // udpProxySocket implements ProxySocket. Close() is implemented by net.UDPConn. When Close() is called,
  161. // no new connections are allowed and existing connections are broken.
  162. // TODO: We could lame-duck this ourselves, if it becomes important.
  163. type udpProxySocket struct {
  164. *net.UDPConn
  165. port int
  166. }
  167. func (udp *udpProxySocket) ListenPort() int {
  168. return udp.port
  169. }
  170. func (udp *udpProxySocket) Addr() net.Addr {
  171. return udp.LocalAddr()
  172. }
  173. // Holds all the known UDP clients that have not timed out.
  174. type ClientCache struct {
  175. Mu sync.Mutex
  176. Clients map[string]net.Conn // addr string -> connection
  177. }
  178. func newClientCache() *ClientCache {
  179. return &ClientCache{Clients: map[string]net.Conn{}}
  180. }
  181. func (udp *udpProxySocket) ProxyLoop(service proxy.ServicePortName, myInfo *ServiceInfo, loadBalancer LoadBalancer) {
  182. var buffer [4096]byte // 4KiB should be enough for most whole-packets
  183. for {
  184. if !myInfo.IsAlive() {
  185. // The service port was closed or replaced.
  186. break
  187. }
  188. // Block until data arrives.
  189. // TODO: Accumulate a histogram of n or something, to fine tune the buffer size.
  190. n, cliAddr, err := udp.ReadFrom(buffer[0:])
  191. if err != nil {
  192. if e, ok := err.(net.Error); ok {
  193. if e.Temporary() {
  194. klog.V(1).Infof("ReadFrom had a temporary failure: %v", err)
  195. continue
  196. }
  197. }
  198. klog.Errorf("ReadFrom failed, exiting ProxyLoop: %v", err)
  199. break
  200. }
  201. // If this is a client we know already, reuse the connection and goroutine.
  202. svrConn, err := udp.getBackendConn(myInfo.ActiveClients, cliAddr, loadBalancer, service, myInfo.Timeout)
  203. if err != nil {
  204. continue
  205. }
  206. // TODO: It would be nice to let the goroutine handle this write, but we don't
  207. // really want to copy the buffer. We could do a pool of buffers or something.
  208. _, err = svrConn.Write(buffer[0:n])
  209. if err != nil {
  210. if !logTimeout(err) {
  211. klog.Errorf("Write failed: %v", err)
  212. // TODO: Maybe tear down the goroutine for this client/server pair?
  213. }
  214. continue
  215. }
  216. err = svrConn.SetDeadline(time.Now().Add(myInfo.Timeout))
  217. if err != nil {
  218. klog.Errorf("SetDeadline failed: %v", err)
  219. continue
  220. }
  221. }
  222. }
  223. func (udp *udpProxySocket) getBackendConn(activeClients *ClientCache, cliAddr net.Addr, loadBalancer LoadBalancer, service proxy.ServicePortName, timeout time.Duration) (net.Conn, error) {
  224. activeClients.Mu.Lock()
  225. defer activeClients.Mu.Unlock()
  226. svrConn, found := activeClients.Clients[cliAddr.String()]
  227. if !found {
  228. // TODO: This could spin up a new goroutine to make the outbound connection,
  229. // and keep accepting inbound traffic.
  230. klog.V(3).Infof("New UDP connection from %s", cliAddr)
  231. var err error
  232. svrConn, err = TryConnectEndpoints(service, cliAddr, "udp", loadBalancer)
  233. if err != nil {
  234. return nil, err
  235. }
  236. if err = svrConn.SetDeadline(time.Now().Add(timeout)); err != nil {
  237. klog.Errorf("SetDeadline failed: %v", err)
  238. return nil, err
  239. }
  240. activeClients.Clients[cliAddr.String()] = svrConn
  241. go func(cliAddr net.Addr, svrConn net.Conn, activeClients *ClientCache, timeout time.Duration) {
  242. defer runtime.HandleCrash()
  243. udp.proxyClient(cliAddr, svrConn, activeClients, timeout)
  244. }(cliAddr, svrConn, activeClients, timeout)
  245. }
  246. return svrConn, nil
  247. }
  248. // This function is expected to be called as a goroutine.
  249. // TODO: Track and log bytes copied, like TCP
  250. func (udp *udpProxySocket) proxyClient(cliAddr net.Addr, svrConn net.Conn, activeClients *ClientCache, timeout time.Duration) {
  251. defer svrConn.Close()
  252. var buffer [4096]byte
  253. for {
  254. n, err := svrConn.Read(buffer[0:])
  255. if err != nil {
  256. if !logTimeout(err) {
  257. klog.Errorf("Read failed: %v", err)
  258. }
  259. break
  260. }
  261. err = svrConn.SetDeadline(time.Now().Add(timeout))
  262. if err != nil {
  263. klog.Errorf("SetDeadline failed: %v", err)
  264. break
  265. }
  266. n, err = udp.WriteTo(buffer[0:n], cliAddr)
  267. if err != nil {
  268. if !logTimeout(err) {
  269. klog.Errorf("WriteTo failed: %v", err)
  270. }
  271. break
  272. }
  273. }
  274. activeClients.Mu.Lock()
  275. delete(activeClients.Clients, cliAddr.String())
  276. activeClients.Mu.Unlock()
  277. }