github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/controller/internal/enforcer/nfqdatapath/ping_tcp.go (about) 1 package nfqdatapath 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "math/rand" 8 "net" 9 "os" 10 "strconv" 11 "syscall" 12 "time" 13 14 "github.com/ghedo/go.pkt/packet/raw" 15 "github.com/ghedo/go.pkt/packet/tcp" 16 "github.com/ghedo/go.pkt/routing" 17 "go.aporeto.io/enforcerd/trireme-lib/collector" 18 enforcerconstants "go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/constants" 19 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/claimsheader" 20 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/connection" 21 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/packet" 22 tpacket "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/packet" 23 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/pingconfig" 24 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/pucontext" 25 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/tokens" 26 "go.aporeto.io/enforcerd/trireme-lib/policy" 27 "go.aporeto.io/gaia" 28 "go.uber.org/zap" 29 ) 30 31 var ( 32 // For unit tests. 33 srcip = getSrcIP 34 dial = dialIP 35 bind = bindRandomPort 36 close = closeRandomPort 37 randUint32 = rand.Uint32 38 since = time.Since 39 isAppListening = isAppListeningInPort 40 41 removeDelay = 10 * time.Second 42 synAckDelay = 3 * time.Second 43 44 _ io.Writer = &pingConn{} 45 ) 46 47 func (d *Datapath) initiatePingHandshake(_ context.Context, context *pucontext.PUContext, pingConfig *policy.PingConfig) error { 48 49 zap.L().Debug("Initiating ping (syn)") 50 51 srcIP, err := srcip(pingConfig.IP) 52 if err != nil { 53 return fmt.Errorf("unable to get source ip: %v", err) 54 } 55 56 conn, err := dial(srcIP, pingConfig.IP) 57 if err != nil { 58 return fmt.Errorf("unable to dial on app syn: %v", err) 59 } 60 defer conn.Close() // nolint: errcheck 61 62 for i := 0; i < pingConfig.Iterations; i++ { 63 if err := d.sendSynPacket(context, pingConfig, conn, srcIP, i); err != nil { 64 return err 65 } 66 } 67 68 return nil 69 } 70 71 // sendSynPacket sends tcp syn packet to the socket. It also dispatches a report. 72 func (d *Datapath) sendSynPacket(context *pucontext.PUContext, pingConfig *policy.PingConfig, conn PingConn, srcIP net.IP, iterationID int) error { 73 74 tcpConn := connection.NewTCPConnection(context, nil) 75 tcpConn.PingConfig = pingconfig.New() 76 77 srcPort, err := bind(tcpConn) 78 if err != nil { 79 return fmt.Errorf("unable to bind free source port: %v", err) 80 } 81 82 claimsHeader := claimsheader.NewClaimsHeader( 83 claimsheader.OptionPing(true), 84 ) 85 86 pingPayload := &policy.PingPayload{ 87 PingID: pingConfig.ID, 88 IterationID: iterationID, 89 NamespaceHash: context.ManagementNamespaceHash(), 90 } 91 92 var tcpData []byte 93 tcpConn.Secrets, tcpConn.Auth.LocalDatapathPrivateKey, tcpData = context.GetSynToken(pingPayload, tcpConn.Auth.Nonce, claimsHeader) 94 95 seqNum := randUint32() 96 p, err := constructTCPPacket(conn, srcIP, pingConfig.IP, srcPort, pingConfig.Port, seqNum, 0, tcp.Syn, tcpData) 97 if err != nil { 98 return fmt.Errorf("unable to construct syn packet: %v", err) 99 } 100 101 // We always get a default policy. 102 _, pkt, _ := context.ApplicationACLPolicyFromAddr(pingConfig.IP, pingConfig.Port, packet.IPProtocolTCP) 103 104 pingErr := "timeout" 105 if e := pingConfig.Error(); e != "" { 106 pingErr = e 107 } 108 109 // RequestTimeout report cached in the connection. This will be sent on 110 // expiration timeout for this connection. 111 tcpConn.PingConfig.SetPingReport(&collector.PingReport{ 112 PingID: pingConfig.ID, 113 IterationID: iterationID, 114 AgentVersion: d.agentVersion.String(), 115 PayloadSize: len(tcpData), 116 PayloadSizeType: gaia.PingProbePayloadSizeTypeTransmitted, 117 Type: gaia.PingProbeTypeRequest, 118 Error: pingErr, 119 PUID: context.ManagementID(), 120 Namespace: context.ManagementNamespace(), 121 Protocol: tpacket.IPProtocolTCP, 122 ServiceType: "L3", 123 FourTuple: flowTuple( 124 tpacket.PacketTypeApplication, 125 srcIP, 126 pingConfig.IP, 127 srcPort, 128 pingConfig.Port, 129 ), 130 SeqNum: seqNum, 131 TargetTCPNetworks: pingConfig.TargetTCPNetworks, 132 ExcludedNetworks: pingConfig.ExcludedNetworks, 133 RemoteNamespaceType: gaia.PingProbeRemoteNamespaceTypeHash, 134 Claims: context.Identity().GetSlice(), 135 ClaimsType: gaia.PingProbeClaimsTypeTransmitted, 136 ACLPolicyID: pkt.PolicyID, 137 ACLPolicyAction: pkt.Action, 138 }) 139 tcpConn.TCPtuple = &connection.TCPTuple{ 140 SourceAddress: srcIP, 141 DestinationAddress: pingConfig.IP, 142 SourcePort: srcPort, 143 DestinationPort: pingConfig.Port, 144 } 145 tcpConn.PingConfig.StartTime = time.Now() 146 tcpConn.PingConfig.SetPingID(pingConfig.ID) 147 tcpConn.PingConfig.SetIterationID(iterationID) 148 tcpConn.PingConfig.SetSeqNum(seqNum) 149 tcpConn.SetState(connection.TCPSynSend) 150 key := flowTuple(tpacket.PacketTypeApplication, srcIP, pingConfig.IP, srcPort, pingConfig.Port) 151 152 d.cachePut(d.tcpClient, key, tcpConn) 153 154 if _, err := conn.Write(p); err != nil { 155 return fmt.Errorf("unable to send syn packet: %v", err) 156 } 157 158 return nil 159 } 160 161 // processPingNetSynPacket should only be called when the packet is recognized as a ping syn packet. 162 func (d *Datapath) processPingNetSynPacket( 163 context *pucontext.PUContext, 164 tcpConn *connection.TCPConnection, 165 tcpPacket *tpacket.Packet, 166 payloadSize int, 167 pkt *policy.FlowPolicy, 168 claims *tokens.ConnectionClaims, 169 ) error { 170 zap.L().Debug("Processing ping network syn packet", zap.String("conn", tcpPacket.L4FlowHash())) 171 172 if tcpConn.GetState() == connection.TCPSynReceived || tcpConn.GetState() == connection.TCPSynAckSend { 173 zap.L().Debug("Dropping duplicate ping syn packets") 174 return errDropPingNetSyn 175 } 176 177 defer func() { 178 tcpConn.SetState(connection.TCPSynReceived) 179 tcpConn.PingConfig.SetSocketClosed(true) 180 tcpConn.PingConfig.SetPingID(claims.P.PingID) 181 tcpConn.PingConfig.SetIterationID(claims.P.IterationID) 182 183 txtID, ok := claims.T.Get(enforcerconstants.TransmitterLabel) 184 if !ok { 185 zap.L().Warn("missing transmitter label") 186 } 187 188 d.cachePut(d.tcpServer, tcpPacket.L4FlowHash(), tcpConn) 189 d.sendRequestRecvReport(txtID, claims.P, tcpPacket, context, pkt, payloadSize, tcpConn.SourceController) 190 }() 191 192 if tcpConn.PingConfig == nil { 193 tcpConn.PingConfig = pingconfig.New() 194 } 195 196 listening, err := isAppListening(tcpPacket.DestPort()) 197 if listening && !pkt.Action.Rejected() { 198 zap.L().Debug("Appplication listening", zap.String("conn", tcpPacket.L4FlowHash()), zap.Error(err)) 199 200 time.AfterFunc(synAckDelay, func() { 201 202 if tcpConn.PingConfig.ApplicationListening() { 203 return 204 } 205 206 if err := d.sendSynAckPacket(context, tcpConn, tcpPacket, claims); err != nil { 207 zap.L().Error("unable to send synack paket", zap.Error(err)) 208 } 209 }) 210 211 return nil 212 } 213 214 if err := d.sendSynAckPacket(context, tcpConn, tcpPacket, claims); err != nil { 215 return err 216 } 217 218 return errDropPingNetSyn 219 } 220 221 func (d *Datapath) sendSynAckPacket( 222 context *pucontext.PUContext, 223 tcpConn *connection.TCPConnection, 224 tcpPacket *tpacket.Packet, 225 claims *tokens.ConnectionClaims, 226 ) error { 227 228 claimsHeader := claimsheader.NewClaimsHeader( 229 claimsheader.OptionPing(true), 230 ) 231 232 pingPayload := &policy.PingPayload{ 233 PingID: claims.P.PingID, 234 IterationID: claims.P.IterationID, 235 NamespaceHash: context.ManagementNamespaceHash(), 236 } 237 238 claimsNew := &tokens.ConnectionClaims{ 239 CT: context.CompressedTags(), 240 LCL: tcpConn.Auth.Nonce[:], 241 RMT: tcpConn.Auth.RemoteNonce, 242 DEKV1: tcpConn.Auth.LocalDatapathPublicKeyV1, 243 SDEKV1: tcpConn.Auth.LocalDatapathPublicKeySignV1, 244 DEKV2: tcpConn.Auth.LocalDatapathPublicKeyV2, 245 SDEKV2: tcpConn.Auth.LocalDatapathPublicKeySignV2, 246 ID: context.ManagementID(), 247 RemoteID: tcpConn.Auth.RemoteContextID, 248 P: pingPayload, 249 } 250 251 tcpData, err := d.tokenAccessor.CreateSynAckPacketToken(tcpConn.Auth.Proto314, claimsNew, tcpConn.EncodedBuf[:], tcpConn.Auth.Nonce[:], claimsHeader, tcpConn.Secrets, tcpConn.Auth.SecretKey) //nolint 252 if err != nil { 253 return fmt.Errorf("unable to create ping synack token: %w", err) 254 } 255 256 conn, err := dial(tcpPacket.DestinationAddress(), tcpPacket.SourceAddress()) 257 if err != nil { 258 return fmt.Errorf("unable to construct synack packet %w", err) 259 } 260 defer conn.Close() // nolint: errcheck 261 262 p, err := constructTCPPacket( 263 conn, 264 tcpPacket.DestinationAddress(), 265 tcpPacket.SourceAddress(), 266 tcpPacket.DestPort(), 267 tcpPacket.SourcePort(), 268 randUint32(), 269 tcpPacket.TCPSeqNum()+1, 270 tcp.Syn|tcp.Ack, 271 tcpData, 272 ) 273 if err != nil { 274 return fmt.Errorf("unable to construct synack packet: %w", err) 275 } 276 277 if _, err := conn.Write(p); err != nil { 278 return fmt.Errorf("unable to send synack packet: %w", err) 279 } 280 281 tcpConn.SetState(connection.TCPSynAckSend) 282 283 return nil 284 } 285 286 // processPingNetSynAckPacket should only be called when the packet is recognized as a ping synack packet. 287 func (d *Datapath) processPingNetSynAckPacket( 288 context *pucontext.PUContext, 289 tcpConn *connection.TCPConnection, 290 tcpPacket *tpacket.Packet, 291 payloadSize int, 292 pkt *policy.FlowPolicy, 293 claims *tokens.ConnectionClaims, 294 ext bool, 295 ) error { 296 zap.L().Debug("Processing ping network synack packet", 297 zap.Bool("externalNetwork", ext), 298 zap.String("conn", tcpPacket.SourcePortHash(packet.PacketTypeNetwork)), 299 ) 300 301 if tcpConn.PingConfig == nil { 302 return errDropPingNetSynAck 303 } 304 305 receiveTime := since(tcpConn.PingConfig.StartTime).String() 306 307 defer func() { 308 tcpConn.SetState(connection.TCPSynAckReceived) 309 310 if !tcpConn.PingConfig.SocketClosed() { 311 defer func() { 312 if err := close(tcpConn); err != nil { 313 zap.L().Warn("unable to close socket", zap.Reflect("fd", tcpConn.PingConfig.SocketFd()), zap.Error(err)) 314 } 315 }() 316 } 317 318 time.AfterFunc(removeDelay, func() { 319 d.cacheRemove(d.tcpClient, tcpPacket.SourcePortHash(packet.PacketTypeNetwork)) 320 }) 321 322 if err := respondWithRstPacket(tcpPacket, nil); err != nil { 323 zap.L().Warn("unable to send rst packet", zap.Error(err)) 324 } 325 }() 326 327 // Drop duplicate synack packets. 328 if tcpConn.GetState() == connection.TCPSynAckReceived { 329 return errDropPingNetSynAck 330 } 331 332 // Synack from externalnetwork. 333 if ext { 334 d.sendExtResponseRecvReport( 335 context, 336 receiveTime, 337 pkt, 338 payloadSize, 339 tcpConn, 340 ) 341 return errDropPingNetSynAck 342 } 343 344 txtID, ok := claims.T.Get(enforcerconstants.TransmitterLabel) 345 if !ok { 346 return fmt.Errorf("missing transmitter label") 347 } 348 349 d.sendResponseRecvReport( 350 txtID, 351 claims.P, 352 context, 353 receiveTime, 354 pkt, 355 payloadSize, 356 tcpConn, 357 tcpConn.DestinationController, 358 ) 359 360 return errDropPingNetSynAck 361 } 362 363 // respondWithRstPacket sends a rst packet in response to tcpPacket. 364 func respondWithRstPacket(tcpPacket *tpacket.Packet, payload []byte) error { 365 366 conn, err := dial(tcpPacket.DestinationAddress(), tcpPacket.SourceAddress()) 367 if err != nil { 368 return fmt.Errorf("unable to dial: %w", err) 369 } 370 defer conn.Close() // nolint: errcheck 371 372 p, err := constructTCPPacket( 373 conn, 374 tcpPacket.DestinationAddress(), 375 tcpPacket.SourceAddress(), 376 tcpPacket.DestPort(), 377 tcpPacket.SourcePort(), 378 tcpPacket.TCPAckNum(), 379 tcpPacket.TCPSeqNum()+1, 380 tcp.Rst, 381 payload, 382 ) 383 if err != nil { 384 return fmt.Errorf("unable to construct rst packet: %w", err) 385 } 386 387 if _, err := conn.Write(p); err != nil { 388 return fmt.Errorf("unable to send rst packet: %w", err) 389 } 390 391 return nil 392 } 393 394 // sendRequestRecvReport sends a report on syn recv state. 395 func (d *Datapath) sendRequestRecvReport( 396 srcPUID string, 397 pingPayload *policy.PingPayload, 398 tcpPacket *tpacket.Packet, 399 context *pucontext.PUContext, 400 pkt *policy.FlowPolicy, 401 payloadSize int, 402 controller string, 403 ) { 404 405 err := "" 406 if pkt.Action.Rejected() { 407 err = collector.PolicyDrop 408 } 409 410 d.sendPingReport( 411 pingPayload.PingID, 412 pingPayload.IterationID, 413 d.agentVersion.String(), 414 tcpPacket.L4FlowHash(), 415 "", 416 srcPUID, 417 context.ManagementID(), 418 context.ManagementNamespace(), 419 pingPayload.NamespaceHash, 420 gaia.PingProbeTypeRequest, 421 payloadSize, 422 pkt.PolicyID, 423 pkt.Action, 424 false, 425 collector.EndPointTypePU, 426 tcpPacket.TCPSeqNum(), 427 controller, 428 true, 429 false, 430 context.Identity().GetSlice(), 431 "", 432 policy.ActionType(0), 433 true, 434 err, 435 ) 436 } 437 438 // sendResponseRecvReport sends a report on synack recv state. 439 func (d *Datapath) sendResponseRecvReport( 440 srcPUID string, 441 pingPayload *policy.PingPayload, 442 context *pucontext.PUContext, 443 rtt string, 444 pkt *policy.FlowPolicy, 445 payloadSize int, 446 tcpConn *connection.TCPConnection, 447 controller string, 448 ) { 449 450 pingErr := "" 451 if !tcpConn.PingConfig.PingReport().TargetTCPNetworks { 452 pingErr = policy.ErrTargetTCPNetworks 453 } 454 455 if tcpConn.PingConfig.PingReport().ExcludedNetworks { 456 pingErr = policy.ErrExcludedNetworks 457 } 458 459 if pkt.Action.Rejected() { 460 pingErr = collector.PolicyDrop 461 } 462 463 d.sendPingReport( 464 pingPayload.PingID, 465 pingPayload.IterationID, 466 d.agentVersion.String(), 467 flowTuple( 468 tpacket.PacketTypeNetwork, 469 tcpConn.TCPtuple.SourceAddress, 470 tcpConn.TCPtuple.DestinationAddress, 471 tcpConn.TCPtuple.SourcePort, 472 tcpConn.TCPtuple.DestinationPort, 473 ), 474 rtt, 475 srcPUID, 476 context.ManagementID(), 477 context.ManagementNamespace(), 478 pingPayload.NamespaceHash, 479 gaia.PingProbeTypeResponse, 480 payloadSize, 481 pkt.PolicyID, 482 pkt.Action, 483 pingPayload.ApplicationListening, 484 collector.EndPointTypePU, 485 tcpConn.PingConfig.SeqNum(), 486 controller, 487 tcpConn.PingConfig.PingReport().TargetTCPNetworks, 488 tcpConn.PingConfig.PingReport().ExcludedNetworks, 489 tcpConn.PingConfig.PingReport().Claims, 490 tcpConn.PingConfig.PingReport().ACLPolicyID, 491 tcpConn.PingConfig.PingReport().ACLPolicyAction, 492 false, 493 pingErr, 494 ) 495 } 496 497 // sendExtResponseRecvReport sends a report on synack from ext net. 498 func (d *Datapath) sendExtResponseRecvReport( 499 context *pucontext.PUContext, 500 rtt string, 501 pkt *policy.FlowPolicy, 502 payloadSize int, 503 tcpConn *connection.TCPConnection, 504 ) { 505 d.sendPingReport( 506 tcpConn.PingConfig.PingID(), 507 tcpConn.PingConfig.IterationID(), 508 d.agentVersion.String(), 509 flowTuple( 510 tpacket.PacketTypeNetwork, 511 tcpConn.TCPtuple.SourceAddress, 512 tcpConn.TCPtuple.DestinationAddress, 513 tcpConn.TCPtuple.SourcePort, 514 tcpConn.TCPtuple.DestinationPort, 515 ), 516 rtt, 517 "", 518 context.ManagementID(), 519 context.ManagementNamespace(), 520 "", 521 gaia.PingProbeTypeResponse, 522 payloadSize, 523 pkt.PolicyID, 524 pkt.Action, 525 true, 526 collector.EndPointTypeExternalIP, 527 tcpConn.PingConfig.SeqNum(), 528 "", 529 tcpConn.PingConfig.PingReport().TargetTCPNetworks, 530 tcpConn.PingConfig.PingReport().ExcludedNetworks, 531 tcpConn.PingConfig.PingReport().Claims, 532 tcpConn.PingConfig.PingReport().ACLPolicyID, 533 tcpConn.PingConfig.PingReport().ACLPolicyAction, 534 false, 535 "", 536 ) 537 } 538 539 func (d *Datapath) sendPingReport( 540 pingID string, 541 iterationID int, 542 agentVersion, 543 fourTuple, 544 rtt, 545 remoteID, 546 puid, 547 ns string, 548 nsHash string, 549 ptype gaia.PingProbeTypeValue, 550 payloadSize int, 551 policyID string, 552 policyAction policy.ActionType, 553 appListening bool, 554 txType collector.EndPointType, 555 seqNum uint32, 556 controller string, 557 tn, 558 en bool, 559 claims []string, 560 aclPolicyID string, 561 aclPolicyAction policy.ActionType, 562 isServer bool, 563 err string, 564 ) { 565 566 report := &collector.PingReport{ 567 PingID: pingID, 568 IterationID: iterationID, 569 AgentVersion: agentVersion, 570 FourTuple: fourTuple, 571 RTT: rtt, 572 PayloadSize: payloadSize, 573 PayloadSizeType: gaia.PingProbePayloadSizeTypeReceived, 574 Type: ptype, 575 PUID: puid, 576 RemotePUID: remoteID, 577 Namespace: ns, 578 RemoteNamespace: nsHash, 579 RemoteNamespaceType: gaia.PingProbeRemoteNamespaceTypeHash, 580 Protocol: tpacket.IPProtocolTCP, 581 ServiceType: "L3", 582 PolicyID: policyID, 583 PolicyAction: policyAction, 584 ApplicationListening: appListening, 585 RemoteEndpointType: txType, 586 SeqNum: seqNum, 587 RemoteController: controller, 588 TargetTCPNetworks: tn, 589 ExcludedNetworks: en, 590 Claims: claims, 591 ClaimsType: gaia.PingProbeClaimsTypeTransmitted, 592 ACLPolicyID: aclPolicyID, 593 ACLPolicyAction: aclPolicyAction, 594 IsServer: isServer, 595 Error: err, 596 } 597 598 d.collector.CollectPingEvent(report) 599 } 600 601 // constructTCPPacket constructs a valid tcp packet that can be sent on wire. 602 func constructTCPPacket(conn PingConn, srcIP, dstIP net.IP, srcPort, dstPort uint16, seqNum, ackNum uint32, flag tcp.Flags, tcpData []byte) ([]byte, error) { 603 604 // tcp. 605 tcpPacket := tcp.Make() 606 tcpPacket.SrcPort = srcPort 607 tcpPacket.DstPort = dstPort 608 tcpPacket.Flags = flag 609 tcpPacket.Seq = seqNum 610 tcpPacket.Ack = ackNum 611 tcpPacket.WindowSize = 0xAAAA 612 tcpPacket.Options = []tcp.Option{ 613 { 614 Type: tcp.MSS, 615 Len: 4, 616 Data: []byte{0x05, 0x8C}, 617 }, 618 } 619 tcpPacket.DataOff = uint8(6) // 5 (header size) + 1 * (4 byte options) 620 621 if len(tcpData) != 0 { 622 tcpPacket.Options = append( 623 tcpPacket.Options, 624 tcp.Option{ 625 Type: 34, // tfo 626 Len: enforcerconstants.TCPAuthenticationOptionBaseLen, 627 Data: make([]byte, 2), 628 }, 629 ) 630 tcpPacket.DataOff += uint8(1) // 6 + 1 * (4 byte options) 631 } 632 633 // payload. 634 payload := raw.Make() 635 payload.Data = tcpData 636 637 // construct the wire packet 638 buf, err := conn.ConstructWirePacket(srcIP, dstIP, tcpPacket, payload) 639 if err != nil { 640 return nil, fmt.Errorf("unable to encode packet to wire format: %v", err) 641 } 642 643 return buf, nil 644 } 645 646 // getSrcIP returns the interface ip that can reach the destination. 647 func getSrcIP(dstIP net.IP) (net.IP, error) { 648 649 route, err := routing.RouteTo(dstIP) 650 if err != nil || route == nil { 651 return nil, fmt.Errorf("no route found for destination %s: %v", dstIP.String(), err) 652 } 653 654 ip, err := route.GetIfaceIPv4Addr() 655 if err != nil { 656 return nil, fmt.Errorf("unable to get interface ip address: %v", err) 657 } 658 659 return ip, nil 660 } 661 662 // flowTuple returns the tuple based on the stage in format <sip:dip:spt:dpt> or <dip:sip:dpt:spt> 663 func flowTuple(stage uint64, srcIP, dstIP net.IP, srcPort, dstPort uint16) string { 664 665 if stage == tpacket.PacketTypeNetwork { 666 return fmt.Sprintf("%s:%s:%s:%s", dstIP.String(), srcIP.String(), strconv.Itoa(int(dstPort)), strconv.Itoa(int(srcPort))) 667 } 668 669 return fmt.Sprintf("%s:%s:%s:%s", srcIP.String(), dstIP.String(), strconv.Itoa(int(srcPort)), strconv.Itoa(int(dstPort))) 670 } 671 672 // isAppListeningInPort returns true if the port is in use by IPv4:TCP app. 673 // It immediately closes the listener socket. 674 // Also returns the actual error for further scrutiny. 675 func isAppListeningInPort(port uint16) (bool, error) { 676 677 addr := fmt.Sprintf(":%d", port) 678 listener, err := net.Listen("tcp4", addr) 679 if listener != nil { 680 listener.Close() // nolint:errcheck 681 } 682 683 return isAddressInUse(err), err 684 } 685 686 // isAddressInUse returns true for and unix.EADDRINUSE or windows.WSAEADDRINUSE errors. 687 func isAddressInUse(err error) bool { 688 689 opErr, ok := err.(*net.OpError) 690 if !ok { 691 return false 692 } 693 694 syscallErr, ok := opErr.Unwrap().(*os.SyscallError) 695 if !ok { 696 return false 697 } 698 699 errNo, ok := syscallErr.Unwrap().(syscall.Errno) 700 if !ok { 701 return false 702 } 703 704 return isAddrInUseErrno(errNo) 705 }