2019-08-07 02:49:29 -04:00
// +build !confonly
2019-08-08 00:27:39 -04:00
2016-05-07 08:08:27 -04:00
package http
2016-05-28 07:44:11 -04:00
2019-07-23 21:15:05 -04:00
import (
2020-05-18 06:27:49 -04:00
"bufio"
2019-07-23 21:15:05 -04:00
"context"
"encoding/base64"
2020-01-07 05:16:22 -05:00
"io"
2020-05-18 06:27:49 -04:00
"net/http"
"net/url"
"sync"
"golang.org/x/net/http2"
2021-02-16 15:31:50 -05:00
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/common/buf"
"github.com/v2fly/v2ray-core/v4/common/bytespool"
"github.com/v2fly/v2ray-core/v4/common/net"
"github.com/v2fly/v2ray-core/v4/common/protocol"
"github.com/v2fly/v2ray-core/v4/common/retry"
"github.com/v2fly/v2ray-core/v4/common/session"
"github.com/v2fly/v2ray-core/v4/common/signal"
"github.com/v2fly/v2ray-core/v4/common/task"
"github.com/v2fly/v2ray-core/v4/features/policy"
"github.com/v2fly/v2ray-core/v4/transport"
"github.com/v2fly/v2ray-core/v4/transport/internet"
"github.com/v2fly/v2ray-core/v4/transport/internet/tls"
2019-07-23 21:15:05 -04:00
)
2016-05-28 07:44:11 -04:00
type Client struct {
2019-07-23 21:15:05 -04:00
serverPicker protocol . ServerPicker
policyManager policy . Manager
}
2020-05-18 06:27:49 -04:00
type h2Conn struct {
rawConn net . Conn
h2Conn * http2 . ClientConn
}
var (
cachedH2Mutex sync . Mutex
cachedH2Conns map [ net . Destination ] h2Conn
)
2019-07-23 21:15:05 -04:00
// NewClient create a new http client based on the given config.
func NewClient ( ctx context . Context , config * ClientConfig ) ( * Client , error ) {
serverList := protocol . NewServerList ( )
for _ , rec := range config . Server {
2020-08-26 07:35:33 -04:00
s , err := protocol . NewServerSpecFromPB ( rec )
2019-07-23 21:15:05 -04:00
if err != nil {
return nil , newError ( "failed to get server spec" ) . Base ( err )
}
serverList . AddServer ( s )
}
if serverList . Size ( ) == 0 {
return nil , newError ( "0 target server" )
}
v := core . MustFromContext ( ctx )
return & Client {
serverPicker : protocol . NewRoundRobinServerPicker ( serverList ) ,
policyManager : v . GetFeature ( policy . ManagerType ( ) ) . ( policy . Manager ) ,
} , nil
}
2019-07-24 10:08:21 -04:00
// Process implements proxy.Outbound.Process. We first create a socket tunnel via HTTP CONNECT method, then redirect all inbound traffic to that tunnel.
2019-07-23 21:15:05 -04:00
func ( c * Client ) Process ( ctx context . Context , link * transport . Link , dialer internet . Dialer ) error {
outbound := session . OutboundFromContext ( ctx )
if outbound == nil || ! outbound . Target . IsValid ( ) {
return newError ( "target not specified." )
}
2020-05-18 06:27:49 -04:00
target := outbound . Target
2020-07-31 11:49:54 -04:00
targetAddr := target . NetAddr ( )
2019-07-23 21:15:05 -04:00
2020-05-18 06:27:49 -04:00
if target . Network == net . Network_UDP {
2019-07-23 21:15:05 -04:00
return newError ( "UDP is not supported by HTTP outbound" )
}
2020-05-18 06:27:49 -04:00
var user * protocol . MemoryUser
2019-07-23 21:15:05 -04:00
var conn internet . Connection
2020-08-15 11:58:58 -04:00
mbuf , _ := link . Reader . ReadMultiBuffer ( )
len := mbuf . Len ( )
firstPayload := bytespool . Alloc ( len )
mbuf , _ = buf . SplitBytes ( mbuf , firstPayload )
firstPayload = firstPayload [ : len ]
buf . ReleaseMulti ( mbuf )
defer bytespool . Free ( firstPayload )
2019-07-23 21:15:05 -04:00
if err := retry . ExponentialBackoff ( 5 , 100 ) . On ( func ( ) error {
2020-05-18 06:27:49 -04:00
server := c . serverPicker . PickServer ( )
2019-07-23 21:15:05 -04:00
dest := server . Destination ( )
2020-05-18 06:27:49 -04:00
user = server . PickUser ( )
2019-07-23 21:15:05 -04:00
2020-08-15 11:58:58 -04:00
netConn , err := setUpHTTPTunnel ( ctx , dest , targetAddr , user , dialer , firstPayload )
2020-05-18 06:27:49 -04:00
if netConn != nil {
2020-10-30 23:03:46 -04:00
if _ , ok := netConn . ( * http2Conn ) ; ! ok {
if _ , err := netConn . Write ( firstPayload ) ; err != nil {
netConn . Close ( )
return err
}
}
2020-05-18 06:27:49 -04:00
conn = internet . Connection ( netConn )
}
return err
2019-07-23 21:15:05 -04:00
} ) ; err != nil {
return newError ( "failed to find an available destination" ) . Base ( err )
}
defer func ( ) {
if err := conn . Close ( ) ; err != nil {
newError ( "failed to closed connection" ) . Base ( err ) . WriteToLog ( session . ExportIDToError ( ctx ) )
}
} ( )
p := c . policyManager . ForLevel ( 0 )
if user != nil {
p = c . policyManager . ForLevel ( user . Level )
}
ctx , cancel := context . WithCancel ( ctx )
timer := signal . CancelAfterInactivity ( ctx , cancel , p . Timeouts . ConnectionIdle )
requestFunc := func ( ) error {
defer timer . SetTimeout ( p . Timeouts . DownlinkOnly )
return buf . Copy ( link . Reader , buf . NewWriter ( conn ) , buf . UpdateActivity ( timer ) )
}
responseFunc := func ( ) error {
defer timer . SetTimeout ( p . Timeouts . UplinkOnly )
2020-03-19 06:39:14 -04:00
return buf . Copy ( buf . NewReader ( conn ) , link . Writer , buf . UpdateActivity ( timer ) )
2019-07-23 21:15:05 -04:00
}
var responseDonePost = task . OnSuccess ( responseFunc , task . Close ( link . Writer ) )
if err := task . Run ( ctx , requestFunc , responseDonePost ) ; err != nil {
return newError ( "connection ends" ) . Base ( err )
}
return nil
}
2020-07-31 11:49:54 -04:00
// setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method
2020-08-15 11:58:58 -04:00
func setUpHTTPTunnel ( ctx context . Context , dest net . Destination , target string , user * protocol . MemoryUser , dialer internet . Dialer , firstPayload [ ] byte ) ( net . Conn , error ) {
2020-07-31 11:49:54 -04:00
req := & http . Request {
Method : http . MethodConnect ,
2020-05-18 06:27:49 -04:00
URL : & url . URL { Host : target } ,
2020-08-15 11:58:58 -04:00
Header : make ( http . Header ) ,
2020-05-18 06:27:49 -04:00
Host : target ,
2020-07-31 11:49:54 -04:00
}
2020-05-18 06:27:49 -04:00
2019-07-23 21:15:05 -04:00
if user != nil && user . Account != nil {
account := user . Account . ( * Account )
auth := account . GetUsername ( ) + ":" + account . GetPassword ( )
2020-05-18 06:27:49 -04:00
req . Header . Set ( "Proxy-Authorization" , "Basic " + base64 . StdEncoding . EncodeToString ( [ ] byte ( auth ) ) )
2019-07-23 21:15:05 -04:00
}
2020-05-18 06:27:49 -04:00
2020-07-31 11:49:54 -04:00
connectHTTP1 := func ( rawConn net . Conn ) ( net . Conn , error ) {
2020-08-15 11:58:58 -04:00
req . Header . Set ( "Proxy-Connection" , "Keep-Alive" )
2020-05-18 06:27:49 -04:00
err := req . Write ( rawConn )
if err != nil {
rawConn . Close ( )
return nil , err
}
2020-12-03 03:07:41 -05:00
resp , err := http . ReadResponse ( bufio . NewReader ( rawConn ) , req )
2020-05-18 06:27:49 -04:00
if err != nil {
rawConn . Close ( )
return nil , err
}
2020-12-03 03:07:41 -05:00
defer resp . Body . Close ( )
2020-05-18 06:27:49 -04:00
if resp . StatusCode != http . StatusOK {
rawConn . Close ( )
return nil , newError ( "Proxy responded with non 200 code: " + resp . Status )
}
return rawConn , nil
2019-07-23 21:15:05 -04:00
}
2020-07-31 11:49:54 -04:00
connectHTTP2 := func ( rawConn net . Conn , h2clientConn * http2 . ClientConn ) ( net . Conn , error ) {
2020-05-18 06:27:49 -04:00
pr , pw := io . Pipe ( )
req . Body = pr
2020-08-15 11:58:58 -04:00
var pErr error
var wg sync . WaitGroup
wg . Add ( 1 )
go func ( ) {
_ , pErr = pw . Write ( firstPayload )
wg . Done ( )
} ( )
2020-11-21 16:05:01 -05:00
resp , err := h2clientConn . RoundTrip ( req ) // nolint: bodyclose
2020-05-18 06:27:49 -04:00
if err != nil {
rawConn . Close ( )
return nil , err
}
2020-08-15 11:58:58 -04:00
wg . Wait ( )
if pErr != nil {
rawConn . Close ( )
return nil , pErr
}
2020-05-18 06:27:49 -04:00
if resp . StatusCode != http . StatusOK {
rawConn . Close ( )
return nil , newError ( "Proxy responded with non 200 code: " + resp . Status )
}
2020-07-31 11:49:54 -04:00
return newHTTP2Conn ( rawConn , pw , resp . Body ) , nil
2020-01-02 08:09:33 -05:00
}
2020-03-19 06:39:14 -04:00
2020-05-18 06:27:49 -04:00
cachedH2Mutex . Lock ( )
2020-09-12 14:01:41 -04:00
cachedConn , cachedConnFound := cachedH2Conns [ dest ]
cachedH2Mutex . Unlock ( )
2020-05-18 06:27:49 -04:00
2020-09-12 14:01:41 -04:00
if cachedConnFound {
2020-07-31 11:49:54 -04:00
rc , cc := cachedConn . rawConn , cachedConn . h2Conn
if cc . CanTakeNewRequest ( ) {
proxyConn , err := connectHTTP2 ( rc , cc )
if err != nil {
return nil , err
2020-05-18 06:27:49 -04:00
}
2020-07-31 11:49:54 -04:00
return proxyConn , nil
2020-05-18 06:27:49 -04:00
}
}
rawConn , err := dialer . Dial ( ctx , dest )
if err != nil {
return nil , err
}
2020-07-31 11:51:08 -04:00
iConn := rawConn
if statConn , ok := iConn . ( * internet . StatCouterConnection ) ; ok {
iConn = statConn . Connection
}
2020-05-18 06:27:49 -04:00
nextProto := ""
2020-07-31 11:51:08 -04:00
if tlsConn , ok := iConn . ( * tls . Conn ) ; ok {
2020-05-18 06:27:49 -04:00
if err := tlsConn . Handshake ( ) ; err != nil {
rawConn . Close ( )
return nil , err
}
nextProto = tlsConn . ConnectionState ( ) . NegotiatedProtocol
}
switch nextProto {
2020-07-31 11:49:54 -04:00
case "" , "http/1.1" :
return connectHTTP1 ( rawConn )
2020-05-18 06:27:49 -04:00
case "h2" :
t := http2 . Transport { }
h2clientConn , err := t . NewClientConn ( rawConn )
if err != nil {
rawConn . Close ( )
return nil , err
}
2020-07-31 11:49:54 -04:00
proxyConn , err := connectHTTP2 ( rawConn , h2clientConn )
2020-05-18 06:27:49 -04:00
if err != nil {
rawConn . Close ( )
return nil , err
}
2020-09-12 14:01:41 -04:00
cachedH2Mutex . Lock ( )
2020-05-18 06:27:49 -04:00
if cachedH2Conns == nil {
cachedH2Conns = make ( map [ net . Destination ] h2Conn )
}
cachedH2Conns [ dest ] = h2Conn {
rawConn : rawConn ,
h2Conn : h2clientConn ,
}
2020-09-12 14:01:41 -04:00
cachedH2Mutex . Unlock ( )
2020-05-18 06:27:49 -04:00
return proxyConn , err
default :
return nil , newError ( "negotiated unsupported application layer protocol: " + nextProto )
}
}
2020-07-31 11:49:54 -04:00
func newHTTP2Conn ( c net . Conn , pipedReqBody * io . PipeWriter , respBody io . ReadCloser ) net . Conn {
2020-05-18 06:27:49 -04:00
return & http2Conn { Conn : c , in : pipedReqBody , out : respBody }
}
type http2Conn struct {
net . Conn
in * io . PipeWriter
out io . ReadCloser
}
func ( h * http2Conn ) Read ( p [ ] byte ) ( n int , err error ) {
return h . out . Read ( p )
}
func ( h * http2Conn ) Write ( p [ ] byte ) ( n int , err error ) {
return h . in . Write ( p )
}
func ( h * http2Conn ) Close ( ) error {
h . in . Close ( )
return h . out . Close ( )
}
2019-07-23 21:15:05 -04:00
func init ( ) {
common . Must ( common . RegisterConfig ( ( * ClientConfig ) ( nil ) , func ( ctx context . Context , config interface { } ) ( interface { } , error ) {
return NewClient ( ctx , config . ( * ClientConfig ) )
} ) )
2016-05-28 07:44:11 -04:00
}