| skipped 148 lines |
149 | 149 | | defer cancel() |
150 | 150 | | |
151 | 151 | | for { |
152 | | - | cont, err := runLocalPortForward(ctx, cli, client, opts) |
| 152 | + | cont, err := runLocalPortForwarding(ctx, cli, client, opts) |
153 | 153 | | if err != nil { |
154 | 154 | | return err |
155 | 155 | | } |
| skipped 6 lines |
162 | 162 | | } |
163 | 163 | | } |
164 | 164 | | |
165 | | - | func runLocalPortForward( |
| 165 | + | func runLocalPortForwarding( |
166 | 166 | | ctx context.Context, |
167 | 167 | | cli cliutil.CLI, |
168 | 168 | | client dockerclient.CommonAPIClient, |
| skipped 101 lines |
270 | 270 | | localPort string |
271 | 271 | | remoteHost string |
272 | 272 | | remotePort string |
| 273 | + | } |
| 274 | + | |
| 275 | + | type directForwarding struct { |
| 276 | + | forwarding |
| 277 | + | targetNetwork string |
| 278 | + | } |
| 279 | + | |
| 280 | + | type sidecarForwarding struct { |
| 281 | + | forwarding |
| 282 | + | targetID string // for netns |
| 283 | + | targetNetwork string |
| 284 | + | targetHost string |
| 285 | + | sidecarPort string |
273 | 286 | | } |
274 | 287 | | |
275 | 288 | | func parseLocalForwardings( |
| skipped 179 lines |
455 | 468 | | for _, fwd := range locals { |
456 | 469 | | wg.Add(1) |
457 | 470 | | |
458 | | - | go func() { |
| 471 | + | go func(fwd forwarding) { |
459 | 472 | | defer wg.Done() |
460 | 473 | | |
461 | 474 | | if err := runLocalForwarder(ctx, cli, client, target, fwd); err != nil { |
462 | 475 | | logrus.Debugf("Forwarding error: %s", err) |
463 | 476 | | errored = true |
464 | 477 | | } |
465 | | - | }() |
| 478 | + | }(fwd) |
466 | 479 | | } |
467 | 480 | | |
468 | 481 | | wg.Wait() |
| skipped 28 lines |
497 | 510 | | return err |
498 | 511 | | } |
499 | 512 | | |
500 | | - | return runLocalForwarderDirect( |
| 513 | + | return runLocalDirectForwarder( |
501 | 514 | | ctx, |
502 | 515 | | cli, |
503 | 516 | | client, |
504 | | - | network, |
505 | | - | forwarding{ |
506 | | - | localHost: fwd.localHost, |
507 | | - | localPort: fwd.localPort, |
508 | | - | remoteHost: remoteIP, |
509 | | - | remotePort: fwd.remotePort, |
| 517 | + | directForwarding{ |
| 518 | + | targetNetwork: network, |
| 519 | + | forwarding: forwarding{ |
| 520 | + | localHost: fwd.localHost, |
| 521 | + | localPort: fwd.localPort, |
| 522 | + | remoteHost: remoteIP, |
| 523 | + | remotePort: fwd.remotePort, |
| 524 | + | }, |
510 | 525 | | }, |
511 | 526 | | ) |
512 | 527 | | } |
| skipped 4 lines |
517 | 532 | | return err |
518 | 533 | | } |
519 | 534 | | |
520 | | - | return runLocalForwarderDirect( |
| 535 | + | return runLocalDirectForwarder( |
521 | 536 | | ctx, |
522 | 537 | | cli, |
523 | 538 | | client, |
524 | | - | network, |
525 | | - | forwarding{ |
526 | | - | localHost: fwd.localHost, |
527 | | - | localPort: fwd.localPort, |
528 | | - | remoteHost: remoteIP, |
529 | | - | remotePort: fwd.remotePort, |
| 539 | + | directForwarding{ |
| 540 | + | targetNetwork: network, |
| 541 | + | forwarding: forwarding{ |
| 542 | + | localHost: fwd.localHost, |
| 543 | + | localPort: fwd.localPort, |
| 544 | + | remoteHost: remoteIP, |
| 545 | + | remotePort: fwd.remotePort, |
| 546 | + | }, |
530 | 547 | | }, |
531 | 548 | | ) |
532 | 549 | | } |
533 | 550 | | |
534 | | - | return runLocalForwarderWithSidecar(ctx, cli, client, target, fwd) |
| 551 | + | // In a multi-network case, pick a random one. |
| 552 | + | var targetNetwork, targetIP string |
| 553 | + | for name, settings := range target.NetworkSettings.Networks { |
| 554 | + | if len(settings.IPAddress) > 0 { |
| 555 | + | targetNetwork = name |
| 556 | + | targetIP = settings.IPAddress |
| 557 | + | break |
| 558 | + | } |
| 559 | + | } |
| 560 | + | if len(targetNetwork) == 0 || len(targetIP) == 0 { |
| 561 | + | return errors.New("target is not attached to any networks") |
| 562 | + | } |
| 563 | + | |
| 564 | + | return runLocalSidecarForwarder( |
| 565 | + | ctx, |
| 566 | + | cli, |
| 567 | + | client, |
| 568 | + | sidecarForwarding{ |
| 569 | + | targetID: target.ID, |
| 570 | + | targetNetwork: targetNetwork, |
| 571 | + | targetHost: targetIP, |
| 572 | + | forwarding: fwd, // as is |
| 573 | + | }, |
| 574 | + | ) |
535 | 575 | | } |
536 | 576 | | |
537 | | - | func runLocalForwarderDirect( |
| 577 | + | func runLocalDirectForwarder( |
538 | 578 | | ctx context.Context, |
539 | 579 | | cli cliutil.CLI, |
540 | 580 | | client dockerclient.CommonAPIClient, |
541 | | - | network string, |
542 | | - | fwd forwarding, |
| 581 | + | fwd directForwarding, |
543 | 582 | | ) error { |
544 | 583 | | // TODO: Try start() N times. |
545 | 584 | | |
546 | | - | forwarderID, err := startLocalForwarder(ctx, client, network, fwd) |
| 585 | + | forwarderID, err := startLocalDirectForwarder(ctx, client, fwd) |
547 | 586 | | defer cleanupContainerIfExist(client, forwarderID) |
548 | 587 | | if err != nil { |
549 | 588 | | return fmt.Errorf("starting forwarder failed: %w", err) |
550 | 589 | | } |
551 | 590 | | |
552 | | - | if err := printOutForwarding(ctx, cli, client, fwd, forwarderID, "", ""); err != nil { |
| 591 | + | if err := printLocalDirectForwarding(ctx, cli, client, fwd, forwarderID); err != nil { |
553 | 592 | | return err |
554 | 593 | | } |
555 | 594 | | |
| skipped 21 lines |
577 | 616 | | } |
578 | 617 | | } |
579 | 618 | | |
580 | | - | func startLocalForwarder( |
| 619 | + | func startLocalDirectForwarder( |
581 | 620 | | ctx context.Context, |
582 | 621 | | client dockerclient.CommonAPIClient, |
583 | | - | network string, |
584 | | - | fwd forwarding, |
| 622 | + | fwd directForwarding, |
585 | 623 | | ) (string, error) { |
586 | 624 | | portMapSpec := fwd.localHost + ":" + fwd.localPort + ":" + fwd.remotePort |
587 | 625 | | exposedPorts, portBindings, err := nat.ParsePortSpecs([]string{portMapSpec}) |
| skipped 15 lines |
603 | 641 | | }, |
604 | 642 | | &container.HostConfig{ |
605 | 643 | | PortBindings: portBindings, |
606 | | - | NetworkMode: container.NetworkMode(network), |
| 644 | + | NetworkMode: container.NetworkMode(fwd.targetNetwork), |
607 | 645 | | }, |
608 | 646 | | nil, |
609 | 647 | | nil, |
| skipped 10 lines |
620 | 658 | | return resp.ID, nil |
621 | 659 | | } |
622 | 660 | | |
623 | | - | func runLocalForwarderWithSidecar( |
| 661 | + | func runLocalSidecarForwarder( |
624 | 662 | | ctx context.Context, |
625 | 663 | | cli cliutil.CLI, |
626 | 664 | | client dockerclient.CommonAPIClient, |
627 | | - | target types.ContainerJSON, |
628 | | - | fwd forwarding, |
| 665 | + | fwd sidecarForwarding, |
629 | 666 | | ) error { |
630 | 667 | | // TODO: Try starting sidecar and forwarder N times. |
631 | 668 | | |
632 | | - | // In a multi-network case, pick a random one. |
633 | | - | var targetNetwork, targetIP string |
634 | | - | for name, settings := range target.NetworkSettings.Networks { |
635 | | - | if len(settings.IPAddress) > 0 { |
636 | | - | targetNetwork = name |
637 | | - | targetIP = settings.IPAddress |
638 | | - | break |
639 | | - | } |
640 | | - | } |
641 | | - | if len(targetIP) == 0 { |
642 | | - | return errors.New("target is not attached to any networks") |
643 | | - | } |
644 | | - | |
645 | | - | sidecarID, targetPort, err := startLocalForwarderSidecar( |
646 | | - | ctx, client, target.ID, fwd.remoteHost, fwd.remotePort, |
| 669 | + | sidecarID, sidecarPort, err := startLocalSidecarForwarder( |
| 670 | + | ctx, client, fwd.targetID, fwd.remoteHost, fwd.remotePort, |
647 | 671 | | ) |
648 | 672 | | defer cleanupContainerIfExist(client, sidecarID) |
649 | 673 | | if err != nil { |
650 | 674 | | return fmt.Errorf("starting forwarder sidecar failed: %w", err) |
651 | 675 | | } |
652 | 676 | | |
653 | | - | forwarderID, err := startLocalForwarder( |
| 677 | + | fwd.sidecarPort = sidecarPort // randomly chosen |
| 678 | + | |
| 679 | + | forwarderID, err := startLocalDirectForwarder( |
654 | 680 | | ctx, |
655 | 681 | | client, |
656 | | - | targetNetwork, |
657 | | - | forwarding{ |
658 | | - | localHost: fwd.localHost, |
659 | | - | localPort: fwd.localPort, |
660 | | - | remoteHost: targetIP, |
661 | | - | remotePort: fmt.Sprintf("%d", targetPort), |
| 682 | + | directForwarding{ |
| 683 | + | targetNetwork: fwd.targetNetwork, |
| 684 | + | forwarding: forwarding{ |
| 685 | + | localHost: fwd.localHost, |
| 686 | + | localPort: fwd.localPort, |
| 687 | + | remoteHost: fwd.targetHost, |
| 688 | + | remotePort: fwd.sidecarPort, |
| 689 | + | }, |
662 | 690 | | }, |
663 | 691 | | ) |
664 | 692 | | defer cleanupContainerIfExist(client, forwarderID) |
| skipped 1 lines |
666 | 694 | | return fmt.Errorf("starting forwarder faield: %w", err) |
667 | 695 | | } |
668 | 696 | | |
669 | | - | if err := printOutForwarding( |
670 | | - | ctx, cli, client, fwd, forwarderID, targetIP, fmt.Sprintf("%d", targetPort), |
671 | | - | ); err != nil { |
| 697 | + | if err := printLocalSidecarForwarding(ctx, cli, client, fwd, forwarderID); err != nil { |
672 | 698 | | return err |
673 | 699 | | } |
674 | 700 | | |
| skipped 37 lines |
712 | 738 | | } |
713 | 739 | | } |
714 | 740 | | |
715 | | - | func startLocalForwarderSidecar( |
| 741 | + | func startLocalSidecarForwarder( |
716 | 742 | | ctx context.Context, |
717 | 743 | | client dockerclient.CommonAPIClient, |
718 | 744 | | targetID string, |
719 | 745 | | remoteHost string, |
720 | 746 | | remotePort string, |
721 | | - | ) (string, int, error) { |
| 747 | + | ) (string, string, error) { |
722 | 748 | | // TODO: This random port may conflict with a port already used by the |
723 | 749 | | // target container. Instead, we should use socat TCP-LISTEN:0 and |
724 | 750 | | // detect what port was assigned by the OS with a separate command. |
725 | | - | randomPort := 32000 + rand.Intn(25000) |
| 751 | + | randomPort := fmt.Sprintf("%d", 32000+rand.Intn(25000)) |
726 | 752 | | resp, err := client.ContainerCreate( |
727 | 753 | | ctx, |
728 | 754 | | &container.Config{ |
729 | 755 | | Image: forwarderImage, |
730 | 756 | | Entrypoint: []string{"socat"}, |
731 | 757 | | Cmd: []string{ |
732 | | - | fmt.Sprintf("TCP4-LISTEN:%d,fork", randomPort), |
| 758 | + | fmt.Sprintf("TCP4-LISTEN:%s,fork", randomPort), |
733 | 759 | | fmt.Sprintf("TCP-CONNECT:%s:%s", remoteHost, remotePort), |
734 | 760 | | }, |
735 | 761 | | Env: []string{"SOCAT_DEFAULT_LISTEN_IP=0.0.0.0"}, |
| skipped 6 lines |
742 | 768 | | "cdebug-fwd-sidecar-"+uuid.ShortID(), |
743 | 769 | | ) |
744 | 770 | | if err != nil { |
745 | | - | return "", 0, fmt.Errorf("cannot create forwarder sidecar container: %w", err) |
| 771 | + | return "", "", fmt.Errorf("cannot create forwarder sidecar container: %w", err) |
746 | 772 | | } |
747 | 773 | | |
748 | 774 | | if err := client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { |
749 | | - | return resp.ID, 0, fmt.Errorf("cannot start forwarder sidecar container: %w", err) |
| 775 | + | return resp.ID, "", fmt.Errorf("cannot start forwarder sidecar container: %w", err) |
750 | 776 | | } |
751 | 777 | | |
752 | 778 | | return resp.ID, randomPort, nil |
753 | 779 | | } |
754 | 780 | | |
755 | | - | func printOutForwarding( |
| 781 | + | func printLocalDirectForwarding( |
756 | 782 | | ctx context.Context, |
757 | 783 | | cli cliutil.CLI, |
758 | 784 | | client dockerclient.CommonAPIClient, |
759 | | - | fwd forwarding, |
| 785 | + | fwd directForwarding, |
760 | 786 | | forwarderID string, |
761 | | - | proxyHost string, |
762 | | - | proxyPort string, |
763 | 787 | | ) error { |
764 | 788 | | if len(fwd.localPort) == 0 { |
765 | 789 | | forwarder, err := client.ContainerInspect(ctx, forwarderID) |
| skipped 11 lines |
777 | 801 | | } |
778 | 802 | | } |
779 | 803 | | |
780 | | - | out := fmt.Sprintf( |
781 | | - | "Forwarding %s:%s to %s:%s", |
782 | | - | fwd.localHost, fwd.localPort, fwd.remoteHost, fwd.remotePort, |
| 804 | + | cli.PrintOut( |
| 805 | + | "Forwarding %s:%s to %s:%s\n", |
| 806 | + | fwd.localHost, fwd.localPort, |
| 807 | + | fwd.remoteHost, fwd.remotePort, |
783 | 808 | | ) |
784 | | - | if len(proxyHost) > 0 { |
785 | | - | out += fmt.Sprintf(" through %s:%s", proxyHost, proxyPort) |
| 809 | + | |
| 810 | + | return nil |
| 811 | + | } |
| 812 | + | |
| 813 | + | func printLocalSidecarForwarding( |
| 814 | + | ctx context.Context, |
| 815 | + | cli cliutil.CLI, |
| 816 | + | client dockerclient.CommonAPIClient, |
| 817 | + | fwd sidecarForwarding, |
| 818 | + | forwarderID string, |
| 819 | + | ) error { |
| 820 | + | if len(fwd.localPort) == 0 { |
| 821 | + | forwarder, err := client.ContainerInspect(ctx, forwarderID) |
| 822 | + | if err != nil { |
| 823 | + | return fmt.Errorf("cannot inspect forwarder container: %w", err) |
| 824 | + | } |
| 825 | + | |
| 826 | + | bindings := lookupPortBindings(forwarder, fwd.sidecarPort) |
| 827 | + | if len(bindings) == 0 { |
| 828 | + | logrus.Debugf("Empty port bindings in forwarder %s", forwarder.ID) |
| 829 | + | fwd.localPort = "<unknown>" |
| 830 | + | } else { |
| 831 | + | // Every forwarder should have just one port exposed. |
| 832 | + | fwd.localPort = bindings[0].HostPort |
| 833 | + | } |
786 | 834 | | } |
787 | | - | cli.PrintOut(out + "\n") |
| 835 | + | |
| 836 | + | cli.PrintOut( |
| 837 | + | "Forwarding %s:%s to %s:%s through %s:%s\n", |
| 838 | + | fwd.localHost, fwd.localPort, |
| 839 | + | fwd.remoteHost, fwd.remotePort, |
| 840 | + | fwd.targetHost, fwd.sidecarPort, |
| 841 | + | ) |
788 | 842 | | |
789 | 843 | | return nil |
790 | 844 | | } |
| skipped 17 lines |