You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

100 lines
2.3 KiB

package proxy
import (
"bufio"
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
type HTTP struct {
*Base
user string
pass string
}
func NewHTTP(addr, user, pass string) (*HTTP, error) {
return &HTTP{
Base: &Base{
addr: addr,
proto: proto.HTTP,
},
user: user,
pass: pass,
}, nil
}
func (h *HTTP) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", h.Addr())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", h.Addr(), err)
}
setKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
err = h.shakeHand(metadata, c)
return
}
func (h *HTTP) shakeHand(metadata *M.Metadata, rw io.ReadWriter) error {
addr := metadata.DestinationAddress()
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{
Host: addr,
},
Host: addr,
Header: http.Header{
"Proxy-Connection": []string{"Keep-Alive"},
},
}
if h.user != "" && h.pass != "" {
req.Header.Set("Proxy-Authorization", fmt.Sprintf("Basic %s", basicAuth(h.user, h.pass)))
}
if err := req.Write(rw); err != nil {
return err
}
resp, err := http.ReadResponse(bufio.NewReader(rw), req)
if err != nil {
return err
}
switch resp.StatusCode {
case http.StatusOK:
return nil
case http.StatusProxyAuthRequired:
return errors.New("HTTP auth required by proxy")
case http.StatusMethodNotAllowed:
return errors.New("CONNECT method not allowed by proxy")
default:
return fmt.Errorf("HTTP connect status: %s", resp.Status)
}
}
// The Basic authentication scheme is based on the model that the client
// needs to authenticate itself with a user-id and a password for each
// protection space ("realm"). The realm value is a free-form string
// that can only be compared for equality with other realms on that
// server. The server will service the request only if it can validate
// the user-id and password for the protection space applying to the
// requested resource.
func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}