@@ -17,24 +17,22 @@ limitations under the License.
1717package server
1818
1919import (
20- "bytes"
2120 "fmt"
2221 "io"
23- "os/exec "
24- "strings "
22+ "net "
23+ "sync "
2524
25+ "github.com/containernetworking/plugins/pkg/ns"
2626 "github.com/pkg/errors"
2727 "github.com/sirupsen/logrus"
2828 "golang.org/x/net/context"
2929 runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
3030
31- ctrdutil "github.com/containerd/cri/pkg/containerd/util"
3231 sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
3332)
3433
3534// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
3635func (c * criService ) PortForward (ctx context.Context , r * runtime.PortForwardRequest ) (retRes * runtime.PortForwardResponse , retErr error ) {
37- // TODO(random-liu): Run a socat container inside the sandbox to do portforward.
3836 sandbox , err := c .sandboxStore .Get (r .GetPodSandboxId ())
3937 if err != nil {
4038 return nil , errors .Wrapf (err , "failed to find sandbox %q" , r .GetPodSandboxId ())
@@ -46,69 +44,48 @@ func (c *criService) PortForward(ctx context.Context, r *runtime.PortForwardRequ
4644 return c .streamServer .GetPortForward (r )
4745}
4846
49- // portForward requires `nsenter` and `socat` on the node, it uses `nsenter` to enter the
50- // sandbox namespace, and run `socat` inside the namespace to forward stream for a specific
51- // port. The `socat` command keeps running until it exits or client disconnect.
47+ // portForward requires it uses netns to enter the sandbox namespace,
48+ // and forward stream for a specific port.
5249func (c * criService ) portForward (id string , port int32 , stream io.ReadWriteCloser ) error {
5350 s , err := c .sandboxStore .Get (id )
5451 if err != nil {
5552 return errors .Wrapf (err , "failed to find sandbox %q in store" , id )
5653 }
57- t , err := s .Container .Task (ctrdutil .NamespacedContext (), nil )
58- if err != nil {
59- return errors .Wrap (err , "failed to get sandbox container task" )
60- }
61- pid := t .Pid ()
62-
63- socat , err := exec .LookPath ("socat" )
64- if err != nil {
65- return errors .Wrap (err , "failed to find socat" )
66- }
67-
68- // Check following links for meaning of the options:
69- // * socat: https://linux.die.net/man/1/socat
70- // * nsenter: http://man7.org/linux/man-pages/man1/nsenter.1.html
71- args := []string {"-t" , fmt .Sprintf ("%d" , pid ), "-n" , socat ,
72- "-" , fmt .Sprintf ("TCP4:localhost:%d" , port )}
73-
74- nsenter , err := exec .LookPath ("nsenter" )
75- if err != nil {
76- return errors .Wrap (err , "failed to find nsenter" )
54+ if s .NetNS == nil || s .NetNS .Closed () {
55+ return errors .Errorf ("network namespace for sandbox %q is closed" , id )
7756 }
7857
79- logrus .Infof ("Executing port forwarding command: %s %s" , nsenter , strings .Join (args , " " ))
80-
81- cmd := exec .Command (nsenter , args ... )
82- cmd .Stdout = stream
83-
84- stderr := new (bytes.Buffer )
85- cmd .Stderr = stderr
86-
87- // If we use Stdin, command.Run() won't return until the goroutine that's copying
88- // from stream finishes. Unfortunately, if you have a client like telnet connected
89- // via port forwarding, as long as the user's telnet client is connected to the user's
90- // local listener that port forwarding sets up, the telnet session never exits. This
91- // means that even if socat has finished running, command.Run() won't ever return
92- // (because the client still has the connection and stream open).
93- //
94- // The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
95- // when the command (socat) exits.
96- in , err := cmd .StdinPipe ()
97- if err != nil {
98- return errors .Wrap (err , "failed to create stdin pipe" )
99- }
100- go func () {
101- if _ , err := io .Copy (in , stream ); err != nil {
102- logrus .WithError (err ).Errorf ("Failed to copy port forward input for %q port %d" , id , port )
58+ err = s .NetNS .GetNs ().Do (func (_ ns.NetNS ) error {
59+ var wg sync.WaitGroup
60+ client , err := net .Dial ("tcp4" , fmt .Sprintf ("localhost:%d" , port ))
61+ if err != nil {
62+ return errors .Wrapf (err , "failed to dial %q" , port )
10363 }
104- in .Close ()
105- logrus .Debugf ("Finish copy port forward input for %q port %d" , id , port )
106- }()
107-
108- if err := cmd .Run (); err != nil {
109- return errors .Errorf ("nsenter command returns error: %v, stderr: %q" , err , stderr .String ())
64+ wg .Add (1 )
65+ go func () {
66+ defer client .Close ()
67+ if _ , err := io .Copy (client , stream ); err != nil {
68+ logrus .WithError (err ).Errorf ("Failed to copy port forward input for %q port %d" , id , port )
69+ }
70+ logrus .Infof ("Finish copy port forward input for %q port %d" , id , port )
71+ wg .Done ()
72+ }()
73+ wg .Add (1 )
74+ go func () {
75+ defer stream .Close ()
76+ if _ , err := io .Copy (stream , client ); err != nil {
77+ logrus .WithError (err ).Errorf ("Failed to copy port forward output for %q port %d" , id , port )
78+ }
79+ logrus .Infof ("Finish copy port forward output for %q port %d" , id , port )
80+ wg .Done ()
81+ }()
82+ wg .Wait ()
83+
84+ return nil
85+ })
86+ if err != nil {
87+ return errors .Wrapf (err , "failed to execute portforward in network namespace %s" , s .NetNS .GetPath ())
11088 }
111-
11289 logrus .Infof ("Finish port forwarding for %q port %d" , id , port )
11390
11491 return nil
0 commit comments