github.com/ipfans/trojan-go@v0.11.0/tunnel/transport/client.go (about)

     1  package transport
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"os/exec"
     7  	"strconv"
     8  
     9  	"github.com/ipfans/trojan-go/common"
    10  	"github.com/ipfans/trojan-go/config"
    11  	"github.com/ipfans/trojan-go/log"
    12  	"github.com/ipfans/trojan-go/tunnel"
    13  	"github.com/ipfans/trojan-go/tunnel/freedom"
    14  )
    15  
    16  // Client implements tunnel.Client
    17  type Client struct {
    18  	serverAddress *tunnel.Address
    19  	cmd           *exec.Cmd
    20  	ctx           context.Context
    21  	cancel        context.CancelFunc
    22  	direct        *freedom.Client
    23  }
    24  
    25  func (c *Client) Close() error {
    26  	c.cancel()
    27  	if c.cmd != nil && c.cmd.Process != nil {
    28  		c.cmd.Process.Kill()
    29  	}
    30  	return nil
    31  }
    32  
    33  func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
    34  	panic("not supported")
    35  }
    36  
    37  // DialConn implements tunnel.Client. It will ignore the params and directly dial to the remote server
    38  func (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) {
    39  	conn, err := c.direct.DialConn(c.serverAddress, nil)
    40  	if err != nil {
    41  		return nil, common.NewError("transport failed to connect to remote server").Base(err)
    42  	}
    43  	return &Conn{
    44  		Conn: conn,
    45  	}, nil
    46  }
    47  
    48  // NewClient creates a transport layer client
    49  func NewClient(ctx context.Context, _ tunnel.Client) (*Client, error) {
    50  	cfg := config.FromContext(ctx, Name).(*Config)
    51  
    52  	var cmd *exec.Cmd
    53  	serverAddress := tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort)
    54  
    55  	if cfg.TransportPlugin.Enabled {
    56  		log.Warn("trojan-go will use transport plugin and work in plain text mode")
    57  		switch cfg.TransportPlugin.Type {
    58  		case "shadowsocks":
    59  			pluginHost := "127.0.0.1"
    60  			pluginPort := common.PickPort("tcp", pluginHost)
    61  			cfg.TransportPlugin.Env = append(
    62  				cfg.TransportPlugin.Env,
    63  				"SS_LOCAL_HOST="+pluginHost,
    64  				"SS_LOCAL_PORT="+strconv.FormatInt(int64(pluginPort), 10),
    65  				"SS_REMOTE_HOST="+cfg.RemoteHost,
    66  				"SS_REMOTE_PORT="+strconv.FormatInt(int64(cfg.RemotePort), 10),
    67  				"SS_PLUGIN_OPTIONS="+cfg.TransportPlugin.Option,
    68  			)
    69  			cfg.RemoteHost = pluginHost
    70  			cfg.RemotePort = pluginPort
    71  			serverAddress = tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort)
    72  			log.Debug("plugin address", serverAddress.String())
    73  			log.Debug("plugin env", cfg.TransportPlugin.Env)
    74  
    75  			cmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)
    76  			cmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)
    77  			cmd.Stdout = os.Stdout
    78  			cmd.Stderr = os.Stdout
    79  			cmd.Start()
    80  		case "other":
    81  			cmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)
    82  			cmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)
    83  			cmd.Stdout = os.Stdout
    84  			cmd.Stderr = os.Stdout
    85  			cmd.Start()
    86  		case "plaintext":
    87  			// do nothing
    88  		default:
    89  			return nil, common.NewError("invalid plugin type: " + cfg.TransportPlugin.Type)
    90  		}
    91  	}
    92  
    93  	direct, err := freedom.NewClient(ctx, nil)
    94  	common.Must(err)
    95  	ctx, cancel := context.WithCancel(ctx)
    96  	client := &Client{
    97  		serverAddress: serverAddress,
    98  		cmd:           cmd,
    99  		ctx:           ctx,
   100  		cancel:        cancel,
   101  		direct:        direct,
   102  	}
   103  	return client, nil
   104  }