add HTTP plugin
This commit is contained in:
parent
afddd2d29e
commit
1760151500
@ -9,7 +9,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
admission_pkg "github.com/go-gost/core/admission"
|
"github.com/go-gost/core/admission"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"github.com/go-gost/x/internal/matcher"
|
"github.com/go-gost/x/internal/matcher"
|
||||||
@ -69,7 +69,7 @@ func LoggerOption(logger logger.Logger) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type admission struct {
|
type localAdmission struct {
|
||||||
ipMatcher matcher.Matcher
|
ipMatcher matcher.Matcher
|
||||||
cidrMatcher matcher.Matcher
|
cidrMatcher matcher.Matcher
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@ -79,14 +79,14 @@ type admission struct {
|
|||||||
|
|
||||||
// NewAdmission creates and initializes a new Admission using matcher patterns as its match rules.
|
// NewAdmission creates and initializes a new Admission using matcher patterns as its match rules.
|
||||||
// The rules will be reversed if the reverse is true.
|
// The rules will be reversed if the reverse is true.
|
||||||
func NewAdmission(opts ...Option) admission_pkg.Admission {
|
func NewAdmission(opts ...Option) admission.Admission {
|
||||||
var options options
|
var options options
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
p := &admission{
|
p := &localAdmission{
|
||||||
cancelFunc: cancel,
|
cancelFunc: cancel,
|
||||||
options: options,
|
options: options,
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ func NewAdmission(opts ...Option) admission_pkg.Admission {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *admission) Admit(ctx context.Context, addr string) bool {
|
func (p *localAdmission) Admit(ctx context.Context, addr string) bool {
|
||||||
if addr == "" || p == nil {
|
if addr == "" || p == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ func (p *admission) Admit(ctx context.Context, addr string) bool {
|
|||||||
p.options.whitelist && matched
|
p.options.whitelist && matched
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *admission) periodReload(ctx context.Context) error {
|
func (p *localAdmission) periodReload(ctx context.Context) error {
|
||||||
period := p.options.period
|
period := p.options.period
|
||||||
if period < time.Second {
|
if period < time.Second {
|
||||||
period = time.Second
|
period = time.Second
|
||||||
@ -138,7 +138,7 @@ func (p *admission) periodReload(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *admission) reload(ctx context.Context) error {
|
func (p *localAdmission) reload(ctx context.Context) error {
|
||||||
v, err := p.load(ctx)
|
v, err := p.load(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -167,7 +167,7 @@ func (p *admission) reload(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *admission) load(ctx context.Context) (patterns []string, err error) {
|
func (p *localAdmission) load(ctx context.Context) (patterns []string, err error) {
|
||||||
if p.options.fileLoader != nil {
|
if p.options.fileLoader != nil {
|
||||||
if lister, ok := p.options.fileLoader.(loader.Lister); ok {
|
if lister, ok := p.options.fileLoader.(loader.Lister); ok {
|
||||||
list, er := lister.List(ctx)
|
list, er := lister.List(ctx)
|
||||||
@ -221,7 +221,7 @@ func (p *admission) load(ctx context.Context) (patterns []string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *admission) parsePatterns(r io.Reader) (patterns []string, err error) {
|
func (p *localAdmission) parsePatterns(r io.Reader) (patterns []string, err error) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -237,14 +237,14 @@ func (p *admission) parsePatterns(r io.Reader) (patterns []string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *admission) parseLine(s string) string {
|
func (p *localAdmission) parseLine(s string) string {
|
||||||
if n := strings.IndexByte(s, '#'); n >= 0 {
|
if n := strings.IndexByte(s, '#'); n >= 0 {
|
||||||
s = s[:n]
|
s = s[:n]
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(s)
|
return strings.TrimSpace(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *admission) matched(addr string) bool {
|
func (p *localAdmission) matched(addr string) bool {
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
defer p.mu.RUnlock()
|
defer p.mu.RUnlock()
|
||||||
|
|
||||||
@ -252,7 +252,7 @@ func (p *admission) matched(addr string) bool {
|
|||||||
p.cidrMatcher.Match(addr)
|
p.cidrMatcher.Match(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *admission) Close() error {
|
func (p *localAdmission) Close() error {
|
||||||
p.cancelFunc()
|
p.cancelFunc()
|
||||||
if p.options.fileLoader != nil {
|
if p.options.fileLoader != nil {
|
||||||
p.options.fileLoader.Close()
|
p.options.fileLoader.Close()
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package admission
|
package admission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
admission_pkg "github.com/go-gost/core/admission"
|
"github.com/go-gost/core/admission"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/plugin/admission/proto"
|
"github.com/go-gost/plugin/admission/proto"
|
||||||
|
"github.com/go-gost/x/internal/util/plugin"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,13 +21,24 @@ type grpcPluginAdmission struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGRPCPluginAdmission creates an Admission plugin based on gRPC.
|
// NewGRPCPluginAdmission creates an Admission plugin based on gRPC.
|
||||||
func NewGRPCPluginAdmission(name string, conn grpc.ClientConnInterface) admission_pkg.Admission {
|
func NewGRPCPluginAdmission(name string, addr string, opts ...plugin.Option) admission.Admission {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "admission",
|
||||||
|
"admission": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
p := &grpcPluginAdmission{
|
p := &grpcPluginAdmission{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
log: logger.Default().WithFields(map[string]any{
|
log: log,
|
||||||
"kind": "admission",
|
|
||||||
"admission": name,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
p.client = proto.NewAdmissionClient(conn)
|
p.client = proto.NewAdmissionClient(conn)
|
||||||
@ -53,3 +68,75 @@ func (p *grpcPluginAdmission) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpAdmissionRequest struct {
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpAdmissionResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPluginAdmission struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPPluginAdmission creates an Admission plugin based on HTTP.
|
||||||
|
func NewHTTPPluginAdmission(name string, url string, opts ...plugin.Option) admission.Admission {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpPluginAdmission{
|
||||||
|
url: url,
|
||||||
|
client: plugin.NewHTTPClient(&options),
|
||||||
|
header: options.Header,
|
||||||
|
log: logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "admission",
|
||||||
|
"admission": name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPluginAdmission) Admit(ctx context.Context, addr string) (ok bool) {
|
||||||
|
if p.client == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := httpAdmissionRequest{
|
||||||
|
Addr: addr,
|
||||||
|
}
|
||||||
|
v, err := json.Marshal(&rb)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, p.url, bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.header != nil {
|
||||||
|
req.Header = p.header.Clone()
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := httpAdmissionResponse{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return res.OK
|
||||||
|
}
|
||||||
|
@ -5,11 +5,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/go-gost/core/admission"
|
"github.com/go-gost/core/admission"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type listener struct {
|
type listener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
admission admission.Admission
|
admission admission.Admission
|
||||||
|
log logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func WrapListener(admission admission.Admission, ln net.Listener) net.Listener {
|
func WrapListener(admission admission.Admission, ln net.Listener) net.Listener {
|
||||||
|
@ -35,7 +35,7 @@ func mwBasicAuth(auther auth.Authenticator) gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
u, p, _ := c.Request.BasicAuth()
|
u, p, _ := c.Request.BasicAuth()
|
||||||
if ok, _ := auther.Authenticate(c, u, p); !ok {
|
if _, ok := auther.Authenticate(c, u, p); !ok {
|
||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,20 +97,20 @@ func NewAuthenticator(opts ...Option) auth.Authenticator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate checks the validity of the provided user-password pair.
|
// Authenticate checks the validity of the provided user-password pair.
|
||||||
func (p *authenticator) Authenticate(ctx context.Context, user, password string) (bool, string) {
|
func (p *authenticator) Authenticate(ctx context.Context, user, password string) (string, bool) {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return true, ""
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
defer p.mu.RUnlock()
|
defer p.mu.RUnlock()
|
||||||
|
|
||||||
if len(p.kvs) == 0 {
|
if len(p.kvs) == 0 {
|
||||||
return false, ""
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
v, ok := p.kvs[user]
|
v, ok := p.kvs[user]
|
||||||
return ok && (v == "" || password == v), ""
|
return "", ok && (v == "" || password == v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *authenticator) periodReload(ctx context.Context) error {
|
func (p *authenticator) periodReload(ctx context.Context) error {
|
||||||
|
108
auth/plugin.go
108
auth/plugin.go
@ -1,12 +1,16 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-gost/core/auth"
|
"github.com/go-gost/core/auth"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/plugin/auth/proto"
|
"github.com/go-gost/plugin/auth/proto"
|
||||||
|
"github.com/go-gost/x/internal/util/plugin"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,13 +21,24 @@ type grpcPluginAuthenticator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGRPCPluginAuthenticator creates an Authenticator plugin based on gRPC.
|
// NewGRPCPluginAuthenticator creates an Authenticator plugin based on gRPC.
|
||||||
func NewGRPCPluginAuthenticator(name string, conn grpc.ClientConnInterface) auth.Authenticator {
|
func NewGRPCPluginAuthenticator(name string, addr string, opts ...plugin.Option) auth.Authenticator {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "auther",
|
||||||
|
"auther": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
p := &grpcPluginAuthenticator{
|
p := &grpcPluginAuthenticator{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
log: logger.Default().WithFields(map[string]any{
|
log: log,
|
||||||
"kind": "auther",
|
|
||||||
"auther": name,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
@ -33,9 +48,9 @@ func NewGRPCPluginAuthenticator(name string, conn grpc.ClientConnInterface) auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate checks the validity of the provided user-password pair.
|
// Authenticate checks the validity of the provided user-password pair.
|
||||||
func (p *grpcPluginAuthenticator) Authenticate(ctx context.Context, user, password string) (bool, string) {
|
func (p *grpcPluginAuthenticator) Authenticate(ctx context.Context, user, password string) (string, bool) {
|
||||||
if p.client == nil {
|
if p.client == nil {
|
||||||
return false, ""
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := p.client.Authenticate(ctx,
|
r, err := p.client.Authenticate(ctx,
|
||||||
@ -45,9 +60,9 @@ func (p *grpcPluginAuthenticator) Authenticate(ctx context.Context, user, passwo
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Error(err)
|
p.log.Error(err)
|
||||||
return false, ""
|
return "", false
|
||||||
}
|
}
|
||||||
return r.Ok, r.Id
|
return r.Id, r.Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *grpcPluginAuthenticator) Close() error {
|
func (p *grpcPluginAuthenticator) Close() error {
|
||||||
@ -56,3 +71,78 @@ func (p *grpcPluginAuthenticator) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpAutherRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpAutherResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPluginAuther struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPPluginAuthenticator creates an Authenticator plugin based on HTTP.
|
||||||
|
func NewHTTPPluginAuthenticator(name string, url string, opts ...plugin.Option) auth.Authenticator {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpPluginAuther{
|
||||||
|
url: url,
|
||||||
|
client: plugin.NewHTTPClient(&options),
|
||||||
|
header: options.Header,
|
||||||
|
log: logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "auther",
|
||||||
|
"auther": name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPluginAuther) Authenticate(ctx context.Context, user, password string) (id string, ok bool) {
|
||||||
|
if p.client == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := httpAutherRequest{
|
||||||
|
Username: user,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
v, err := json.Marshal(&rb)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, p.url, bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.header != nil {
|
||||||
|
req.Header = p.header.Clone()
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := httpAutherResponse{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return res.ID, res.OK
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
bypass_pkg "github.com/go-gost/core/bypass"
|
"github.com/go-gost/core/bypass"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"github.com/go-gost/x/internal/matcher"
|
"github.com/go-gost/x/internal/matcher"
|
||||||
@ -77,7 +77,7 @@ func LoggerOption(logger logger.Logger) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type bypass struct {
|
type localBypass struct {
|
||||||
ipMatcher matcher.Matcher
|
ipMatcher matcher.Matcher
|
||||||
cidrMatcher matcher.Matcher
|
cidrMatcher matcher.Matcher
|
||||||
domainMatcher matcher.Matcher
|
domainMatcher matcher.Matcher
|
||||||
@ -89,7 +89,7 @@ type bypass struct {
|
|||||||
|
|
||||||
// NewBypass creates and initializes a new Bypass.
|
// NewBypass creates and initializes a new Bypass.
|
||||||
// The rules will be reversed if the reverse option is true.
|
// The rules will be reversed if the reverse option is true.
|
||||||
func NewBypass(opts ...Option) bypass_pkg.Bypass {
|
func NewBypass(opts ...Option) bypass.Bypass {
|
||||||
var options options
|
var options options
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&options)
|
opt(&options)
|
||||||
@ -97,7 +97,7 @@ func NewBypass(opts ...Option) bypass_pkg.Bypass {
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
|
||||||
bp := &bypass{
|
bp := &localBypass{
|
||||||
cancelFunc: cancel,
|
cancelFunc: cancel,
|
||||||
options: options,
|
options: options,
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ func NewBypass(opts ...Option) bypass_pkg.Bypass {
|
|||||||
return bp
|
return bp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *bypass) periodReload(ctx context.Context) error {
|
func (bp *localBypass) periodReload(ctx context.Context) error {
|
||||||
period := bp.options.period
|
period := bp.options.period
|
||||||
if period < time.Second {
|
if period < time.Second {
|
||||||
period = time.Second
|
period = time.Second
|
||||||
@ -133,7 +133,7 @@ func (bp *bypass) periodReload(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *bypass) reload(ctx context.Context) error {
|
func (bp *localBypass) reload(ctx context.Context) error {
|
||||||
v, err := bp.load(ctx)
|
v, err := bp.load(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -171,7 +171,7 @@ func (bp *bypass) reload(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *bypass) load(ctx context.Context) (patterns []string, err error) {
|
func (bp *localBypass) load(ctx context.Context) (patterns []string, err error) {
|
||||||
if bp.options.fileLoader != nil {
|
if bp.options.fileLoader != nil {
|
||||||
if lister, ok := bp.options.fileLoader.(loader.Lister); ok {
|
if lister, ok := bp.options.fileLoader.(loader.Lister); ok {
|
||||||
list, er := lister.List(ctx)
|
list, er := lister.List(ctx)
|
||||||
@ -224,7 +224,7 @@ func (bp *bypass) load(ctx context.Context) (patterns []string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *bypass) parsePatterns(r io.Reader) (patterns []string, err error) {
|
func (bp *localBypass) parsePatterns(r io.Reader) (patterns []string, err error) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -240,7 +240,7 @@ func (bp *bypass) parsePatterns(r io.Reader) (patterns []string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *bypass) Contains(ctx context.Context, addr string) bool {
|
func (bp *localBypass) Contains(ctx context.Context, addr string) bool {
|
||||||
if addr == "" || bp == nil {
|
if addr == "" || bp == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -260,14 +260,14 @@ func (bp *bypass) Contains(ctx context.Context, addr string) bool {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *bypass) parseLine(s string) string {
|
func (bp *localBypass) parseLine(s string) string {
|
||||||
if n := strings.IndexByte(s, '#'); n >= 0 {
|
if n := strings.IndexByte(s, '#'); n >= 0 {
|
||||||
s = s[:n]
|
s = s[:n]
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(s)
|
return strings.TrimSpace(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *bypass) matched(addr string) bool {
|
func (bp *localBypass) matched(addr string) bool {
|
||||||
bp.mu.RLock()
|
bp.mu.RLock()
|
||||||
defer bp.mu.RUnlock()
|
defer bp.mu.RUnlock()
|
||||||
|
|
||||||
@ -280,7 +280,7 @@ func (bp *bypass) matched(addr string) bool {
|
|||||||
bp.wildcardMatcher.Match(addr)
|
bp.wildcardMatcher.Match(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *bypass) Close() error {
|
func (bp *localBypass) Close() error {
|
||||||
bp.cancelFunc()
|
bp.cancelFunc()
|
||||||
if bp.options.fileLoader != nil {
|
if bp.options.fileLoader != nil {
|
||||||
bp.options.fileLoader.Close()
|
bp.options.fileLoader.Close()
|
||||||
|
101
bypass/plugin.go
101
bypass/plugin.go
@ -1,13 +1,17 @@
|
|||||||
package bypass
|
package bypass
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
bypass_pkg "github.com/go-gost/core/bypass"
|
"github.com/go-gost/core/bypass"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/plugin/bypass/proto"
|
"github.com/go-gost/plugin/bypass/proto"
|
||||||
auth_util "github.com/go-gost/x/internal/util/auth"
|
auth_util "github.com/go-gost/x/internal/util/auth"
|
||||||
|
"github.com/go-gost/x/internal/util/plugin"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,13 +22,24 @@ type grpcPluginBypass struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGRPCPluginBypass creates a Bypass plugin based on gRPC.
|
// NewGRPCPluginBypass creates a Bypass plugin based on gRPC.
|
||||||
func NewGRPCPluginBypass(name string, conn grpc.ClientConnInterface) bypass_pkg.Bypass {
|
func NewGRPCPluginBypass(name string, addr string, opts ...plugin.Option) bypass.Bypass {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "bypass",
|
||||||
|
"bypass": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
p := &grpcPluginBypass{
|
p := &grpcPluginBypass{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
log: logger.Default().WithFields(map[string]any{
|
log: log,
|
||||||
"kind": "bypass",
|
|
||||||
"bypass": name,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
p.client = proto.NewBypassClient(conn)
|
p.client = proto.NewBypassClient(conn)
|
||||||
@ -55,3 +70,77 @@ func (p *grpcPluginBypass) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpBypassRequest struct {
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Client string `json:"client"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpBypassResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPluginBypass struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPPluginBypass creates an Bypass plugin based on HTTP.
|
||||||
|
func NewHTTPPluginBypass(name string, url string, opts ...plugin.Option) bypass.Bypass {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpPluginBypass{
|
||||||
|
url: url,
|
||||||
|
client: plugin.NewHTTPClient(&options),
|
||||||
|
header: options.Header,
|
||||||
|
log: logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "bypass",
|
||||||
|
"bypass": name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPluginBypass) Contains(ctx context.Context, addr string) (ok bool) {
|
||||||
|
if p.client == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := httpBypassRequest{
|
||||||
|
Addr: addr,
|
||||||
|
Client: string(auth_util.IDFromContext(ctx)),
|
||||||
|
}
|
||||||
|
v, err := json.Marshal(&rb)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, p.url, bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.header != nil {
|
||||||
|
req.Header = p.header.Clone()
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := httpBypassResponse{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return res.OK
|
||||||
|
}
|
||||||
|
@ -110,9 +110,11 @@ type TLSConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PluginConfig struct {
|
type PluginConfig struct {
|
||||||
Addr string `json:"addr"`
|
Type string `json:"type"`
|
||||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
Addr string `json:"addr"`
|
||||||
Token string `yaml:",omitempty" json:"token,omitempty"`
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||||
|
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
||||||
|
Token string `yaml:",omitempty" json:"token,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AutherConfig struct {
|
type AutherConfig struct {
|
||||||
|
@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-gost/core/admission"
|
"github.com/go-gost/core/admission"
|
||||||
"github.com/go-gost/core/auth"
|
"github.com/go-gost/core/auth"
|
||||||
@ -26,6 +28,7 @@ import (
|
|||||||
xhosts "github.com/go-gost/x/hosts"
|
xhosts "github.com/go-gost/x/hosts"
|
||||||
xingress "github.com/go-gost/x/ingress"
|
xingress "github.com/go-gost/x/ingress"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
|
"github.com/go-gost/x/internal/util/plugin"
|
||||||
xconn "github.com/go-gost/x/limiter/conn"
|
xconn "github.com/go-gost/x/limiter/conn"
|
||||||
xrate "github.com/go-gost/x/limiter/rate"
|
xrate "github.com/go-gost/x/limiter/rate"
|
||||||
xtraffic "github.com/go-gost/x/limiter/traffic"
|
xtraffic "github.com/go-gost/x/limiter/traffic"
|
||||||
@ -61,11 +64,27 @@ func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Plugin != nil {
|
if cfg.Plugin != nil {
|
||||||
c, err := newPluginConn(cfg.Plugin)
|
var tlsCfg *tls.Config
|
||||||
if err != nil {
|
if cfg.Plugin.TLS != nil {
|
||||||
logger.Default().Error(err)
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch cfg.Plugin.Type {
|
||||||
|
case "http":
|
||||||
|
return auth_impl.NewHTTPPluginAuthenticator(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return auth_impl.NewGRPCPluginAuthenticator(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return auth_impl.NewGRPCPluginAuthenticator(cfg.Name, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
@ -189,11 +208,27 @@ func ParseAdmission(cfg *config.AdmissionConfig) admission.Admission {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Plugin != nil {
|
if cfg.Plugin != nil {
|
||||||
c, err := newPluginConn(cfg.Plugin)
|
var tlsCfg *tls.Config
|
||||||
if err != nil {
|
if cfg.Plugin.TLS != nil {
|
||||||
logger.Default().Error(err)
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return admission_impl.NewHTTPPluginAdmission(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return admission_impl.NewGRPCPluginAdmission(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return admission_impl.NewGRPCPluginAdmission(cfg.Name, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []admission_impl.Option{
|
opts := []admission_impl.Option{
|
||||||
@ -232,11 +267,27 @@ func ParseBypass(cfg *config.BypassConfig) bypass.Bypass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Plugin != nil {
|
if cfg.Plugin != nil {
|
||||||
c, err := newPluginConn(cfg.Plugin)
|
var tlsCfg *tls.Config
|
||||||
if err != nil {
|
if cfg.Plugin.TLS != nil {
|
||||||
logger.Default().Error(err)
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return bypass_impl.NewHTTPPluginBypass(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return bypass_impl.NewGRPCPluginBypass(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return bypass_impl.NewGRPCPluginBypass(cfg.Name, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []bypass_impl.Option{
|
opts := []bypass_impl.Option{
|
||||||
@ -275,12 +326,27 @@ func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Plugin != nil {
|
if cfg.Plugin != nil {
|
||||||
c, err := newPluginConn(cfg.Plugin)
|
var tlsCfg *tls.Config
|
||||||
if err != nil {
|
if cfg.Plugin.TLS != nil {
|
||||||
logger.Default().Error(err)
|
tlsCfg = &tls.Config{
|
||||||
return nil, err
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return resolver_impl.NewHTTPPluginResolver(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
), nil
|
||||||
|
default:
|
||||||
|
return resolver_impl.NewGRPCPluginResolver(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return resolver_impl.NewGRPCPluginResolver(cfg.Name, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var nameservers []resolver_impl.NameServer
|
var nameservers []resolver_impl.NameServer
|
||||||
@ -313,11 +379,27 @@ func ParseHosts(cfg *config.HostsConfig) hosts.HostMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Plugin != nil {
|
if cfg.Plugin != nil {
|
||||||
c, err := newPluginConn(cfg.Plugin)
|
var tlsCfg *tls.Config
|
||||||
if err != nil {
|
if cfg.Plugin.TLS != nil {
|
||||||
logger.Default().Error(err)
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return xhosts.NewHTTPPluginHostMapper(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return xhosts.NewGRPCPluginHostMapper(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return xhosts.NewGRPCPluginHostMapper(cfg.Name, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var mappings []xhosts.Mapping
|
var mappings []xhosts.Mapping
|
||||||
@ -379,11 +461,27 @@ func ParseIngress(cfg *config.IngressConfig) ingress.Ingress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Plugin != nil {
|
if cfg.Plugin != nil {
|
||||||
c, err := newPluginConn(cfg.Plugin)
|
var tlsCfg *tls.Config
|
||||||
if err != nil {
|
if cfg.Plugin.TLS != nil {
|
||||||
logger.Default().Error(err)
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return xingress.NewHTTPPluginIngress(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return xingress.NewGRPCPluginIngress(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return xingress.NewGRPCPluginIngress(cfg.Name, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []xingress.Rule
|
var rules []xingress.Rule
|
||||||
@ -441,11 +539,27 @@ func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Plugin != nil {
|
if cfg.Plugin != nil {
|
||||||
c, err := newPluginConn(cfg.Plugin)
|
var tlsCfg *tls.Config
|
||||||
if err != nil {
|
if cfg.Plugin.TLS != nil {
|
||||||
logger.Default().Error(err)
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return xrecorder.NewHTTPPluginRecorder(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return xrecorder.NewGRPCPluginRecorder(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return xrecorder.NewGRPCPluginRecorder(cfg.Name, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.File != nil && cfg.File.Path != "" {
|
if cfg.File != nil && cfg.File.Path != "" {
|
||||||
@ -644,7 +758,7 @@ func ParseRateLimiter(cfg *config.LimiterConfig) (lim rate.RateLimiter) {
|
|||||||
return xrate.NewRateLimiter(opts...)
|
return xrate.NewRateLimiter(opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPluginConn(cfg *config.PluginConfig) (*grpc.ClientConn, error) {
|
func newGRPCPluginConn(cfg *config.PluginConfig) (*grpc.ClientConn, error) {
|
||||||
grpcOpts := []grpc.DialOption{
|
grpcOpts := []grpc.DialOption{
|
||||||
// grpc.WithBlock(),
|
// grpc.WithBlock(),
|
||||||
grpc.WithConnectParams(grpc.ConnectParams{
|
grpc.WithConnectParams(grpc.ConnectParams{
|
||||||
@ -681,3 +795,26 @@ func (c *rpcCredentials) GetRequestMetadata(ctx context.Context, uri ...string)
|
|||||||
func (c *rpcCredentials) RequireTransportSecurity() bool {
|
func (c *rpcCredentials) RequireTransportSecurity() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newHTTPPluginClient(cfg *config.PluginConfig) *http.Client {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := &http.Transport{}
|
||||||
|
if cfg.TLS != nil {
|
||||||
|
if cfg.TLS.Secure {
|
||||||
|
tr.TLSClientConfig = &tls.Config{
|
||||||
|
ServerName: cfg.TLS.ServerName,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tr.TLSClientConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &http.Client{
|
||||||
|
Timeout: cfg.Timeout,
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ require (
|
|||||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||||
github.com/gin-contrib/cors v1.3.1
|
github.com/gin-contrib/cors v1.3.1
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/go-gost/core v0.0.0-20230919141921-a1419ec2f4d1
|
github.com/go-gost/core v0.0.0-20230920145336-6d0e88635be9
|
||||||
github.com/go-gost/gosocks4 v0.0.1
|
github.com/go-gost/gosocks4 v0.0.1
|
||||||
github.com/go-gost/gosocks5 v0.4.0
|
github.com/go-gost/gosocks5 v0.4.0
|
||||||
github.com/go-gost/plugin v0.0.0-20230919143240-0e42c7c67eaa
|
github.com/go-gost/plugin v0.0.0-20230919143240-0e42c7c67eaa
|
||||||
|
2
go.sum
2
go.sum
@ -103,6 +103,8 @@ github.com/go-gost/core v0.0.0-20230918131208-c258a114c40b h1:kqALaNXbbYyKFlcLj3
|
|||||||
github.com/go-gost/core v0.0.0-20230918131208-c258a114c40b/go.mod h1:db6lBY+DkC3ct4OJfclsKnQwQmcv1B9NnMnpI2MNUwY=
|
github.com/go-gost/core v0.0.0-20230918131208-c258a114c40b/go.mod h1:db6lBY+DkC3ct4OJfclsKnQwQmcv1B9NnMnpI2MNUwY=
|
||||||
github.com/go-gost/core v0.0.0-20230919141921-a1419ec2f4d1 h1:tV5Ra3bBU5R9Mcg9lGzMQeVeLcnCFEEiE6UNnx6F46k=
|
github.com/go-gost/core v0.0.0-20230919141921-a1419ec2f4d1 h1:tV5Ra3bBU5R9Mcg9lGzMQeVeLcnCFEEiE6UNnx6F46k=
|
||||||
github.com/go-gost/core v0.0.0-20230919141921-a1419ec2f4d1/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
|
github.com/go-gost/core v0.0.0-20230919141921-a1419ec2f4d1/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
|
||||||
|
github.com/go-gost/core v0.0.0-20230920145336-6d0e88635be9 h1:VHka8LcdBJmM7Yv2bjQO5kctF0T9O4E/PVzgkdk0Vdo=
|
||||||
|
github.com/go-gost/core v0.0.0-20230920145336-6d0e88635be9/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
|
||||||
github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
|
github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
|
||||||
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
|
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
|
||||||
github.com/go-gost/gosocks5 v0.4.0 h1:EIrOEkpJez4gwHrMa33frA+hHXJyevjp47thpMQsJzI=
|
github.com/go-gost/gosocks5 v0.4.0 h1:EIrOEkpJez4gwHrMa33frA+hHXJyevjp47thpMQsJzI=
|
||||||
|
@ -209,7 +209,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, log l
|
|||||||
|
|
||||||
if auther := target.Options().Auther; auther != nil {
|
if auther := target.Options().Auther; auther != nil {
|
||||||
username, password, _ := req.BasicAuth()
|
username, password, _ := req.BasicAuth()
|
||||||
ok, id := auther.Authenticate(ctx, username, password)
|
id, ok := auther.Authenticate(ctx, username, password)
|
||||||
if !ok {
|
if !ok {
|
||||||
resp.StatusCode = http.StatusUnauthorized
|
resp.StatusCode = http.StatusUnauthorized
|
||||||
resp.Header.Set("WWW-Authenticate", "Basic")
|
resp.Header.Set("WWW-Authenticate", "Basic")
|
||||||
|
@ -206,7 +206,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, log l
|
|||||||
|
|
||||||
if auther := target.Options().Auther; auther != nil {
|
if auther := target.Options().Auther; auther != nil {
|
||||||
username, password, _ := req.BasicAuth()
|
username, password, _ := req.BasicAuth()
|
||||||
ok, id := auther.Authenticate(ctx, username, password)
|
id, ok := auther.Authenticate(ctx, username, password)
|
||||||
if !ok {
|
if !ok {
|
||||||
resp.StatusCode = http.StatusUnauthorized
|
resp.StatusCode = http.StatusUnauthorized
|
||||||
resp.Header.Set("WWW-Authenticate", "Basic")
|
resp.Header.Set("WWW-Authenticate", "Basic")
|
||||||
|
@ -146,7 +146,7 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
|
|||||||
resp.Header = http.Header{}
|
resp.Header = http.Header{}
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, id := h.authenticate(ctx, conn, req, resp, log)
|
id, ok := h.authenticate(ctx, conn, req, resp, log)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -269,12 +269,12 @@ func (h *httpHandler) basicProxyAuth(proxyAuth string, log logger.Logger) (usern
|
|||||||
return cs[:s], cs[s+1:], true
|
return cs[:s], cs[s+1:], true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpHandler) authenticate(ctx context.Context, conn net.Conn, req *http.Request, resp *http.Response, log logger.Logger) (ok bool, token string) {
|
func (h *httpHandler) authenticate(ctx context.Context, conn net.Conn, req *http.Request, resp *http.Response, log logger.Logger) (id string, ok bool) {
|
||||||
u, p, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization"), log)
|
u, p, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization"), log)
|
||||||
if h.options.Auther == nil {
|
if h.options.Auther == nil {
|
||||||
return true, ""
|
return "", true
|
||||||
}
|
}
|
||||||
if ok, token = h.options.Auther.Authenticate(ctx, u, p); ok {
|
if id, ok = h.options.Auther.Authenticate(ctx, u, p); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
|
|||||||
Body: io.NopCloser(bytes.NewReader([]byte{})),
|
Body: io.NopCloser(bytes.NewReader([]byte{})),
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, id := h.authenticate(ctx, w, req, resp, log)
|
id, ok := h.authenticate(ctx, w, req, resp, log)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -254,12 +254,12 @@ func (h *http2Handler) basicProxyAuth(proxyAuth string) (username, password stri
|
|||||||
return cs[:s], cs[s+1:], true
|
return cs[:s], cs[s+1:], true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *http2Handler) authenticate(ctx context.Context, w http.ResponseWriter, r *http.Request, resp *http.Response, log logger.Logger) (ok bool, token string) {
|
func (h *http2Handler) authenticate(ctx context.Context, w http.ResponseWriter, r *http.Request, resp *http.Response, log logger.Logger) (id string, ok bool) {
|
||||||
u, p, _ := h.basicProxyAuth(r.Header.Get("Proxy-Authorization"))
|
u, p, _ := h.basicProxyAuth(r.Header.Get("Proxy-Authorization"))
|
||||||
if h.options.Auther == nil {
|
if h.options.Auther == nil {
|
||||||
return true, ""
|
return "", true
|
||||||
}
|
}
|
||||||
if ok, token = h.options.Auther.Authenticate(ctx, u, p); ok {
|
if id, ok = h.options.Auther.Authenticate(ctx, u, p); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle
|
|||||||
}
|
}
|
||||||
|
|
||||||
if h.options.Auther != nil {
|
if h.options.Auther != nil {
|
||||||
ok, id := h.options.Auther.Authenticate(ctx, user, pass)
|
id, ok := h.options.Auther.Authenticate(ctx, user, pass)
|
||||||
if !ok {
|
if !ok {
|
||||||
resp.Status = relay.StatusUnauthorized
|
resp.Status = relay.StatusUnauthorized
|
||||||
resp.WriteTo(conn)
|
resp.WriteTo(conn)
|
||||||
|
@ -92,7 +92,7 @@ func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl
|
|||||||
conn.SetReadDeadline(time.Time{})
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
if h.options.Auther != nil {
|
if h.options.Auther != nil {
|
||||||
ok, id := h.options.Auther.Authenticate(ctx, string(req.Userid), "")
|
id, ok := h.options.Auther.Authenticate(ctx, string(req.Userid), "")
|
||||||
if !ok {
|
if !ok {
|
||||||
resp := gosocks4.NewReply(gosocks4.RejectedUserid, nil)
|
resp := gosocks4.NewReply(gosocks4.RejectedUserid, nil)
|
||||||
log.Trace(resp)
|
log.Trace(resp)
|
||||||
|
@ -68,7 +68,7 @@ func (s *serverSelector) OnSelected(method uint8, conn net.Conn) (string, net.Co
|
|||||||
var id string
|
var id string
|
||||||
if s.Authenticator != nil {
|
if s.Authenticator != nil {
|
||||||
var ok bool
|
var ok bool
|
||||||
ok, id = s.Authenticator.Authenticate(context.Background(), req.Username, req.Password)
|
id, ok = s.Authenticator.Authenticate(context.Background(), req.Username, req.Password)
|
||||||
if !ok {
|
if !ok {
|
||||||
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure)
|
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure)
|
||||||
if err := resp.Write(conn); err != nil {
|
if err := resp.Write(conn); err != nil {
|
||||||
|
@ -135,7 +135,7 @@ func (h *tunHandler) transportServer(ctx context.Context, tun io.ReadWriter, con
|
|||||||
ok := true
|
ok := true
|
||||||
key := bytes.TrimRight((*b)[4:20], "\x00")
|
key := bytes.TrimRight((*b)[4:20], "\x00")
|
||||||
for _, ip := range peerIPs {
|
for _, ip := range peerIPs {
|
||||||
if ok, _ = auther.Authenticate(ctx, ip.String(), string(key)); !ok {
|
if _, ok = auther.Authenticate(ctx, ip.String(), string(key)); !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
109
hosts/plugin.go
109
hosts/plugin.go
@ -1,14 +1,18 @@
|
|||||||
package hosts
|
package hosts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-gost/core/hosts"
|
"github.com/go-gost/core/hosts"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/plugin/hosts/proto"
|
"github.com/go-gost/plugin/hosts/proto"
|
||||||
auth_util "github.com/go-gost/x/internal/util/auth"
|
auth_util "github.com/go-gost/x/internal/util/auth"
|
||||||
|
"github.com/go-gost/x/internal/util/plugin"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,13 +23,23 @@ type grpcPluginHostMapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGRPCPluginHostMapper creates a HostMapper plugin based on gRPC.
|
// NewGRPCPluginHostMapper creates a HostMapper plugin based on gRPC.
|
||||||
func NewGRPCPluginHostMapper(name string, conn grpc.ClientConnInterface) hosts.HostMapper {
|
func NewGRPCPluginHostMapper(name string, addr string, opts ...plugin.Option) hosts.HostMapper {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "hosts",
|
||||||
|
"hosts": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
p := &grpcPluginHostMapper{
|
p := &grpcPluginHostMapper{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
log: logger.Default().WithFields(map[string]any{
|
log: log,
|
||||||
"kind": "hosts",
|
|
||||||
"hosts": name,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
p.client = proto.NewHostMapperClient(conn)
|
p.client = proto.NewHostMapperClient(conn)
|
||||||
@ -65,3 +79,88 @@ func (p *grpcPluginHostMapper) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpHostMapperRequest struct {
|
||||||
|
Network string `json:"network"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Client string `json:"client"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpHostMapperResponse struct {
|
||||||
|
IPs []string `json:"ips"`
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPluginHostMapper struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPPluginHostMapper creates an HostMapper plugin based on HTTP.
|
||||||
|
func NewHTTPPluginHostMapper(name string, url string, opts ...plugin.Option) hosts.HostMapper {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpPluginHostMapper{
|
||||||
|
url: url,
|
||||||
|
client: plugin.NewHTTPClient(&options),
|
||||||
|
header: options.Header,
|
||||||
|
log: logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "hosts",
|
||||||
|
"hosts": name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPluginHostMapper) Lookup(ctx context.Context, network, host string) (ips []net.IP, ok bool) {
|
||||||
|
p.log.Debugf("lookup %s/%s", host, network)
|
||||||
|
|
||||||
|
if p.client == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := httpHostMapperRequest{
|
||||||
|
Network: network,
|
||||||
|
Host: host,
|
||||||
|
Client: string(auth_util.IDFromContext(ctx)),
|
||||||
|
}
|
||||||
|
v, err := json.Marshal(&rb)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, p.url, bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.header != nil {
|
||||||
|
req.Header = p.header.Clone()
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := httpHostMapperResponse{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range res.IPs {
|
||||||
|
if ip := net.ParseIP(s); ip != nil {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips, res.OK
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ingress_pkg "github.com/go-gost/core/ingress"
|
"github.com/go-gost/core/ingress"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@ -74,7 +74,7 @@ func LoggerOption(logger logger.Logger) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ingress struct {
|
type localIngress struct {
|
||||||
rules map[string]Rule
|
rules map[string]Rule
|
||||||
cancelFunc context.CancelFunc
|
cancelFunc context.CancelFunc
|
||||||
options options
|
options options
|
||||||
@ -82,7 +82,7 @@ type ingress struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewIngress creates and initializes a new Ingress.
|
// NewIngress creates and initializes a new Ingress.
|
||||||
func NewIngress(opts ...Option) ingress_pkg.Ingress {
|
func NewIngress(opts ...Option) ingress.Ingress {
|
||||||
var options options
|
var options options
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&options)
|
opt(&options)
|
||||||
@ -90,7 +90,7 @@ func NewIngress(opts ...Option) ingress_pkg.Ingress {
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
|
||||||
ing := &ingress{
|
ing := &localIngress{
|
||||||
cancelFunc: cancel,
|
cancelFunc: cancel,
|
||||||
options: options,
|
options: options,
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ func NewIngress(opts ...Option) ingress_pkg.Ingress {
|
|||||||
return ing
|
return ing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ing *ingress) periodReload(ctx context.Context) error {
|
func (ing *localIngress) periodReload(ctx context.Context) error {
|
||||||
period := ing.options.period
|
period := ing.options.period
|
||||||
if period < time.Second {
|
if period < time.Second {
|
||||||
period = time.Second
|
period = time.Second
|
||||||
@ -126,7 +126,7 @@ func (ing *ingress) periodReload(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ing *ingress) reload(ctx context.Context) error {
|
func (ing *localIngress) reload(ctx context.Context) error {
|
||||||
rules := make(map[string]Rule)
|
rules := make(map[string]Rule)
|
||||||
|
|
||||||
fn := func(rule Rule) {
|
fn := func(rule Rule) {
|
||||||
@ -160,7 +160,7 @@ func (ing *ingress) reload(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ing *ingress) load(ctx context.Context) (rules []Rule, err error) {
|
func (ing *localIngress) load(ctx context.Context) (rules []Rule, err error) {
|
||||||
if ing.options.fileLoader != nil {
|
if ing.options.fileLoader != nil {
|
||||||
if lister, ok := ing.options.fileLoader.(loader.Lister); ok {
|
if lister, ok := ing.options.fileLoader.(loader.Lister); ok {
|
||||||
list, er := lister.List(ctx)
|
list, er := lister.List(ctx)
|
||||||
@ -211,7 +211,7 @@ func (ing *ingress) load(ctx context.Context) (rules []Rule, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ing *ingress) parseRules(r io.Reader) (rules []Rule, err error) {
|
func (ing *localIngress) parseRules(r io.Reader) (rules []Rule, err error) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ func (ing *ingress) parseRules(r io.Reader) (rules []Rule, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ing *ingress) Get(ctx context.Context, host string) string {
|
func (ing *localIngress) Get(ctx context.Context, host string) string {
|
||||||
if host == "" || ing == nil {
|
if host == "" || ing == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ func (ing *ingress) Get(ctx context.Context, host string) string {
|
|||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ing *ingress) lookup(host string) string {
|
func (ing *localIngress) lookup(host string) string {
|
||||||
if ing == nil || len(ing.rules) == 0 {
|
if ing == nil || len(ing.rules) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -278,7 +278,7 @@ func (ing *ingress) lookup(host string) string {
|
|||||||
return ing.rules[host].Endpoint
|
return ing.rules[host].Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ing *ingress) parseLine(s string) (rule Rule) {
|
func (ing *localIngress) parseLine(s string) (rule Rule) {
|
||||||
line := strings.Replace(s, "\t", " ", -1)
|
line := strings.Replace(s, "\t", " ", -1)
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if n := strings.IndexByte(line, '#'); n >= 0 {
|
if n := strings.IndexByte(line, '#'); n >= 0 {
|
||||||
@ -300,7 +300,7 @@ func (ing *ingress) parseLine(s string) (rule Rule) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ing *ingress) Close() error {
|
func (ing *localIngress) Close() error {
|
||||||
ing.cancelFunc()
|
ing.cancelFunc()
|
||||||
if ing.options.fileLoader != nil {
|
if ing.options.fileLoader != nil {
|
||||||
ing.options.fileLoader.Close()
|
ing.options.fileLoader.Close()
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package ingress
|
package ingress
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
ingress_pkg "github.com/go-gost/core/ingress"
|
"github.com/go-gost/core/ingress"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/plugin/ingress/proto"
|
"github.com/go-gost/plugin/ingress/proto"
|
||||||
|
"github.com/go-gost/x/internal/util/plugin"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,14 +20,25 @@ type grpcPluginIngress struct {
|
|||||||
log logger.Logger
|
log logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGRPCPluginIngress creates a ingress plugin based on gRPC.
|
// NewGRPCPluginIngress creates an Ingress plugin based on gRPC.
|
||||||
func NewGRPCPluginIngress(name string, conn grpc.ClientConnInterface) ingress_pkg.Ingress {
|
func NewGRPCPluginIngress(name string, addr string, opts ...plugin.Option) ingress.Ingress {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "ingress",
|
||||||
|
"ingress": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
p := &grpcPluginIngress{
|
p := &grpcPluginIngress{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
log: logger.Default().WithFields(map[string]any{
|
log: log,
|
||||||
"kind": "ingress",
|
|
||||||
"ingress": name,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
p.client = proto.NewIngressClient(conn)
|
p.client = proto.NewIngressClient(conn)
|
||||||
@ -53,3 +68,75 @@ func (p *grpcPluginIngress) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpIngressRequest struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpIngressResponse struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPluginIngress struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPPluginIngress creates an Ingress plugin based on HTTP.
|
||||||
|
func NewHTTPPluginIngress(name string, url string, opts ...plugin.Option) ingress.Ingress {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpPluginIngress{
|
||||||
|
url: url,
|
||||||
|
client: plugin.NewHTTPClient(&options),
|
||||||
|
header: options.Header,
|
||||||
|
log: logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "ingress",
|
||||||
|
"ingress": name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPluginIngress) Get(ctx context.Context, host string) (endpoint string) {
|
||||||
|
if p.client == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := httpIngressRequest{
|
||||||
|
Host: host,
|
||||||
|
}
|
||||||
|
v, err := json.Marshal(&rb)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, p.url, bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.header != nil {
|
||||||
|
req.Header = p.header.Clone()
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := httpIngressResponse{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return res.Endpoint
|
||||||
|
}
|
||||||
|
91
internal/util/plugin/plugin.go
Normal file
91
internal/util/plugin/plugin.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/backoff"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Token string
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
Header http.Header
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(opts *Options)
|
||||||
|
|
||||||
|
func TokenOption(token string) Option {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.Token = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TLSConfigOption(cfg *tls.Config) Option {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.TLSConfig = cfg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HeaderOption(header http.Header) Option {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.Header = header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeoutOption(timeout time.Duration) Option {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.Timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGRPCConn(addr string, opts *Options) (*grpc.ClientConn, error) {
|
||||||
|
grpcOpts := []grpc.DialOption{
|
||||||
|
// grpc.WithBlock(),
|
||||||
|
grpc.WithConnectParams(grpc.ConnectParams{
|
||||||
|
Backoff: backoff.DefaultConfig,
|
||||||
|
}),
|
||||||
|
grpc.FailOnNonTempDialError(true),
|
||||||
|
}
|
||||||
|
if opts.TLSConfig != nil {
|
||||||
|
grpcOpts = append(grpcOpts,
|
||||||
|
grpc.WithAuthority(opts.TLSConfig.ServerName),
|
||||||
|
grpc.WithTransportCredentials(credentials.NewTLS(opts.TLSConfig)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
}
|
||||||
|
if opts.Token != "" {
|
||||||
|
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(&rpcCredentials{token: opts.Token}))
|
||||||
|
}
|
||||||
|
return grpc.Dial(addr, grpcOpts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rpcCredentials struct {
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rpcCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||||
|
return map[string]string{
|
||||||
|
"token": c.token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rpcCredentials) RequireTransportSecurity() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPClient(opts *Options) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Timeout: opts.Timeout,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: opts.TLSConfig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ func PasswordCallback(au auth.Authenticator) PasswordCallbackFunc {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||||
if ok, _ := au.Authenticate(context.Background(), conn.User(), string(password)); ok {
|
if _, ok := au.Authenticate(context.Background(), conn.User(), string(password)); ok {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("password rejected for %s", conn.User())
|
return nil, fmt.Errorf("password rejected for %s", conn.User())
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
package recorder
|
package recorder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/core/recorder"
|
"github.com/go-gost/core/recorder"
|
||||||
"github.com/go-gost/plugin/recorder/proto"
|
"github.com/go-gost/plugin/recorder/proto"
|
||||||
|
"github.com/go-gost/x/internal/util/plugin"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,14 +22,25 @@ type grpcPluginRecorder struct {
|
|||||||
log logger.Logger
|
log logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGRPCPluginRecorder creates a plugin recorder.
|
// NewGRPCPluginRecorder creates a Recorder plugin based on gRPC.
|
||||||
func NewGRPCPluginRecorder(name string, conn grpc.ClientConnInterface) recorder.Recorder {
|
func NewGRPCPluginRecorder(name string, addr string, opts ...plugin.Option) recorder.Recorder {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "recorder",
|
||||||
|
"recorder": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
p := &grpcPluginRecorder{
|
p := &grpcPluginRecorder{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
log: logger.Default().WithFields(map[string]any{
|
log: log,
|
||||||
"kind": "recorder",
|
|
||||||
"recorder": name,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
p.client = proto.NewRecorderClient(conn)
|
p.client = proto.NewRecorderClient(conn)
|
||||||
@ -53,3 +70,79 @@ func (p *grpcPluginRecorder) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpRecorderRequest struct {
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpRecorderResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPluginRecorder struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPPluginRecorder creates an Recorder plugin based on HTTP.
|
||||||
|
func NewHTTPPluginRecorder(name string, url string, opts ...plugin.Option) recorder.Recorder {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpPluginRecorder{
|
||||||
|
url: url,
|
||||||
|
client: plugin.NewHTTPClient(&options),
|
||||||
|
header: options.Header,
|
||||||
|
log: logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "recorder",
|
||||||
|
"recorder": name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPluginRecorder) Record(ctx context.Context, b []byte) error {
|
||||||
|
if len(b) == 0 || p.client == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := httpRecorderRequest{
|
||||||
|
Data: b,
|
||||||
|
}
|
||||||
|
v, err := json.Marshal(&rb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, p.url, bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.header != nil {
|
||||||
|
req.Header = p.header.Clone()
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("%s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := httpRecorderResponse{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !res.OK {
|
||||||
|
return errors.New("record failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -30,10 +30,10 @@ type autherWrapper struct {
|
|||||||
r *autherRegistry
|
r *autherRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *autherWrapper) Authenticate(ctx context.Context, user, password string) (bool, string) {
|
func (w *autherWrapper) Authenticate(ctx context.Context, user, password string) (string, bool) {
|
||||||
v := w.r.get(w.name)
|
v := w.r.get(w.name)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return true, ""
|
return "", true
|
||||||
}
|
}
|
||||||
return v.Authenticate(ctx, user, password)
|
return v.Authenticate(ctx, user, password)
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
resolver_pkg "github.com/go-gost/core/resolver"
|
"github.com/go-gost/core/resolver"
|
||||||
"github.com/go-gost/plugin/resolver/proto"
|
"github.com/go-gost/plugin/resolver/proto"
|
||||||
auth_util "github.com/go-gost/x/internal/util/auth"
|
auth_util "github.com/go-gost/x/internal/util/auth"
|
||||||
|
"github.com/go-gost/x/internal/util/plugin"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,13 +25,23 @@ type grpcPluginResolver struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGRPCPluginResolver creates a Resolver plugin based on gRPC.
|
// NewGRPCPluginResolver creates a Resolver plugin based on gRPC.
|
||||||
func NewGRPCPluginResolver(name string, conn grpc.ClientConnInterface) (resolver_pkg.Resolver, error) {
|
func NewGRPCPluginResolver(name string, addr string, opts ...plugin.Option) (resolver.Resolver, error) {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "resolver",
|
||||||
|
"resolover": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
p := &grpcPluginResolver{
|
p := &grpcPluginResolver{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
log: logger.Default().WithFields(map[string]any{
|
log: log,
|
||||||
"kind": "resolver",
|
|
||||||
"resolver": name,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
p.client = proto.NewResolverClient(conn)
|
p.client = proto.NewResolverClient(conn)
|
||||||
@ -64,3 +80,93 @@ func (p *grpcPluginResolver) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpResolverRequest struct {
|
||||||
|
Network string `json:"network"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Client string `json:"client"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpResolverResponse struct {
|
||||||
|
IPs []string `json:"ips"`
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPluginResolver struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPPluginResolver creates an Resolver plugin based on HTTP.
|
||||||
|
func NewHTTPPluginResolver(name string, url string, opts ...plugin.Option) resolver.Resolver {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpPluginResolver{
|
||||||
|
url: url,
|
||||||
|
client: plugin.NewHTTPClient(&options),
|
||||||
|
header: options.Header,
|
||||||
|
log: logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "resolver",
|
||||||
|
"resolver": name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPluginResolver) Resolve(ctx context.Context, network, host string) (ips []net.IP, err error) {
|
||||||
|
p.log.Debugf("resolve %s/%s", host, network)
|
||||||
|
|
||||||
|
if p.client == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := httpResolverRequest{
|
||||||
|
Network: network,
|
||||||
|
Host: host,
|
||||||
|
Client: string(auth_util.IDFromContext(ctx)),
|
||||||
|
}
|
||||||
|
v, err := json.Marshal(&rb)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, p.url, bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.header != nil {
|
||||||
|
req.Header = p.header.Clone()
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("%s", resp.Status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := httpResolverResponse{}
|
||||||
|
if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !res.OK {
|
||||||
|
return nil, errors.New("resolve failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range res.IPs {
|
||||||
|
if ip := net.ParseIP(s); ip != nil {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-gost/core/chain"
|
"github.com/go-gost/core/chain"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
resolverpkg "github.com/go-gost/core/resolver"
|
"github.com/go-gost/core/resolver"
|
||||||
resolver_util "github.com/go-gost/x/internal/util/resolver"
|
resolver_util "github.com/go-gost/x/internal/util/resolver"
|
||||||
"github.com/go-gost/x/resolver/exchanger"
|
"github.com/go-gost/x/resolver/exchanger"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@ -52,13 +52,13 @@ func LoggerOption(logger logger.Logger) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type resolver struct {
|
type localResolver struct {
|
||||||
servers []NameServer
|
servers []NameServer
|
||||||
cache *resolver_util.Cache
|
cache *resolver_util.Cache
|
||||||
options options
|
options options
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver(nameservers []NameServer, opts ...Option) (resolverpkg.Resolver, error) {
|
func NewResolver(nameservers []NameServer, opts ...Option) (resolver.Resolver, error) {
|
||||||
options := options{}
|
options := options{}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&options)
|
opt(&options)
|
||||||
@ -92,14 +92,14 @@ func NewResolver(nameservers []NameServer, opts ...Option) (resolverpkg.Resolver
|
|||||||
cache := resolver_util.NewCache().
|
cache := resolver_util.NewCache().
|
||||||
WithLogger(options.logger)
|
WithLogger(options.logger)
|
||||||
|
|
||||||
return &resolver{
|
return &localResolver{
|
||||||
servers: servers,
|
servers: servers,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
options: options,
|
options: options,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolver) Resolve(ctx context.Context, network, host string) (ips []net.IP, err error) {
|
func (r *localResolver) Resolve(ctx context.Context, network, host string) (ips []net.IP, err error) {
|
||||||
if ip := net.ParseIP(host); ip != nil {
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
return []net.IP{ip}, nil
|
return []net.IP{ip}, nil
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ func (r *resolver) Resolve(ctx context.Context, network, host string) (ips []net
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolver) resolve(ctx context.Context, server *NameServer, host string) (ips []net.IP, err error) {
|
func (r *localResolver) resolve(ctx context.Context, server *NameServer, host string) (ips []net.IP, err error) {
|
||||||
if server == nil {
|
if server == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -144,19 +144,19 @@ func (r *resolver) resolve(ctx context.Context, server *NameServer, host string)
|
|||||||
return r.resolve6(ctx, server, host)
|
return r.resolve6(ctx, server, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolver) resolve4(ctx context.Context, server *NameServer, host string) (ips []net.IP, err error) {
|
func (r *localResolver) resolve4(ctx context.Context, server *NameServer, host string) (ips []net.IP, err error) {
|
||||||
mq := dns.Msg{}
|
mq := dns.Msg{}
|
||||||
mq.SetQuestion(dns.Fqdn(host), dns.TypeA)
|
mq.SetQuestion(dns.Fqdn(host), dns.TypeA)
|
||||||
return r.resolveIPs(ctx, server, &mq)
|
return r.resolveIPs(ctx, server, &mq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolver) resolve6(ctx context.Context, server *NameServer, host string) (ips []net.IP, err error) {
|
func (r *localResolver) resolve6(ctx context.Context, server *NameServer, host string) (ips []net.IP, err error) {
|
||||||
mq := dns.Msg{}
|
mq := dns.Msg{}
|
||||||
mq.SetQuestion(dns.Fqdn(host), dns.TypeAAAA)
|
mq.SetQuestion(dns.Fqdn(host), dns.TypeAAAA)
|
||||||
return r.resolveIPs(ctx, server, &mq)
|
return r.resolveIPs(ctx, server, &mq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolver) resolveIPs(ctx context.Context, server *NameServer, mq *dns.Msg) (ips []net.IP, err error) {
|
func (r *localResolver) resolveIPs(ctx context.Context, server *NameServer, mq *dns.Msg) (ips []net.IP, err error) {
|
||||||
key := resolver_util.NewCacheKey(&mq.Question[0])
|
key := resolver_util.NewCacheKey(&mq.Question[0])
|
||||||
mr, ttl := r.cache.Load(key)
|
mr, ttl := r.cache.Load(key)
|
||||||
if ttl <= 0 {
|
if ttl <= 0 {
|
||||||
@ -180,7 +180,7 @@ func (r *resolver) resolveIPs(ctx context.Context, server *NameServer, mq *dns.M
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolver) exchange(ctx context.Context, ex exchanger.Exchanger, mq *dns.Msg) (mr *dns.Msg, err error) {
|
func (r *localResolver) exchange(ctx context.Context, ex exchanger.Exchanger, mq *dns.Msg) (mr *dns.Msg, err error) {
|
||||||
query, err := mq.Pack()
|
query, err := mq.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user