mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-11-14 04:33:18 +00:00
!feat(vless): IP restriction
Beta, only works for vless for now and it's not perfect needs a lot of testing.
This commit is contained in:
parent
34b68518fd
commit
3d692eb208
|
@ -42,6 +42,7 @@ type tcpWorker struct {
|
||||||
sniffingConfig *proxyman.SniffingConfig
|
sniffingConfig *proxyman.SniffingConfig
|
||||||
uplinkCounter stats.Counter
|
uplinkCounter stats.Counter
|
||||||
downlinkCounter stats.Counter
|
downlinkCounter stats.Counter
|
||||||
|
ipLimitPool map[session.ID]*stat.UserIpRestriction
|
||||||
|
|
||||||
hub internet.Listener
|
hub internet.Listener
|
||||||
|
|
||||||
|
@ -104,9 +105,18 @@ func (w *tcpWorker) callback(conn stat.Connection) {
|
||||||
}
|
}
|
||||||
ctx = session.ContextWithContent(ctx, content)
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
|
|
||||||
if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil {
|
// Add this IP address to the pool for futher IP limit check
|
||||||
|
w.ipLimitPool[sid] = &stat.UserIpRestriction{
|
||||||
|
IpAddress: net.IP(conn.RemoteAddr().Network()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher, &w.ipLimitPool, w.ipLimitPool[sid]); err != nil {
|
||||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deletes the IP address from the pool after the connection ends
|
||||||
|
delete(w.ipLimitPool, sid)
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
@ -116,6 +126,9 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *tcpWorker) Start() error {
|
func (w *tcpWorker) Start() error {
|
||||||
|
if len(w.ipLimitPool) == 0 {
|
||||||
|
w.ipLimitPool = make(map[session.ID]*stat.UserIpRestriction)
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
|
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
|
||||||
go w.callback(conn)
|
go w.callback(conn)
|
||||||
|
@ -244,6 +257,7 @@ type udpWorker struct {
|
||||||
sniffingConfig *proxyman.SniffingConfig
|
sniffingConfig *proxyman.SniffingConfig
|
||||||
uplinkCounter stats.Counter
|
uplinkCounter stats.Counter
|
||||||
downlinkCounter stats.Counter
|
downlinkCounter stats.Counter
|
||||||
|
ipLimitPool map[session.ID]*stat.UserIpRestriction
|
||||||
|
|
||||||
checker *task.Periodic
|
checker *task.Periodic
|
||||||
activeConn map[connID]*udpConn
|
activeConn map[connID]*udpConn
|
||||||
|
@ -326,9 +340,19 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
|
||||||
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
||||||
}
|
}
|
||||||
ctx = session.ContextWithContent(ctx, content)
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil {
|
|
||||||
|
// Add this IP address to the pool for futher IP limit check
|
||||||
|
w.ipLimitPool[sid] = &stat.UserIpRestriction{
|
||||||
|
IpAddress: net.IP(conn.RemoteAddr().Network()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher, &w.ipLimitPool, w.ipLimitPool[sid]); err != nil {
|
||||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deletes the IP address from the pool after the connection ends
|
||||||
|
delete(w.ipLimitPool, sid)
|
||||||
|
|
||||||
conn.Close()
|
conn.Close()
|
||||||
// conn not removed by checker TODO may be lock worker here is better
|
// conn not removed by checker TODO may be lock worker here is better
|
||||||
if !conn.inactive {
|
if !conn.inactive {
|
||||||
|
@ -379,6 +403,9 @@ func (w *udpWorker) clean() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *udpWorker) Start() error {
|
func (w *udpWorker) Start() error {
|
||||||
|
if len(w.ipLimitPool) == 0 {
|
||||||
|
w.ipLimitPool = make(map[session.ID]*stat.UserIpRestriction)
|
||||||
|
}
|
||||||
w.activeConn = make(map[connID]*udpConn, 16)
|
w.activeConn = make(map[connID]*udpConn, 16)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
h, err := udp.ListenUDP(ctx, w.address, w.port, w.stream, udp.HubCapacity(256))
|
h, err := udp.ListenUDP(ctx, w.address, w.port, w.stream, udp.HubCapacity(256))
|
||||||
|
@ -478,7 +505,7 @@ func (w *dsWorker) callback(conn stat.Connection) {
|
||||||
}
|
}
|
||||||
ctx = session.ContextWithContent(ctx, content)
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
|
|
||||||
if err := w.proxy.Process(ctx, net.Network_UNIX, conn, w.dispatcher); err != nil {
|
if err := w.proxy.Process(ctx, net.Network_UNIX, conn, w.dispatcher, nil, nil); err != nil {
|
||||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
|
|
|
@ -27,6 +27,7 @@ func (u *User) ToMemoryUser() (*MemoryUser, error) {
|
||||||
Account: account,
|
Account: account,
|
||||||
Email: u.Email,
|
Email: u.Email,
|
||||||
Level: u.Level,
|
Level: u.Level,
|
||||||
|
IpLimit: u.Ips,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,4 +37,5 @@ type MemoryUser struct {
|
||||||
Account Account
|
Account Account
|
||||||
Email string
|
Email string
|
||||||
Level uint32
|
Level uint32
|
||||||
|
IpLimit uint32
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ type User struct {
|
||||||
// Protocol specific account information. Must be the account proto in one of
|
// Protocol specific account information. Must be the account proto in one of
|
||||||
// the proxies.
|
// the proxies.
|
||||||
Account *serial.TypedMessage `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"`
|
Account *serial.TypedMessage `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"`
|
||||||
|
// Allowed IPs
|
||||||
|
Ips uint32 `protobuf:"varint,4,opt,name=ips,proto3" json:"ips,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *User) Reset() {
|
func (x *User) Reset() {
|
||||||
|
@ -87,6 +89,13 @@ func (x *User) GetAccount() *serial.TypedMessage {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *User) GetIps() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ips
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
var File_common_protocol_user_proto protoreflect.FileDescriptor
|
var File_common_protocol_user_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_common_protocol_user_proto_rawDesc = []byte{
|
var file_common_protocol_user_proto_rawDesc = []byte{
|
||||||
|
@ -95,20 +104,21 @@ var file_common_protocol_user_proto_rawDesc = []byte{
|
||||||
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
||||||
0x6f, 0x6c, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61,
|
0x6f, 0x6c, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61,
|
||||||
0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e,
|
0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6e, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14, 0x0a,
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x80, 0x01, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14,
|
||||||
0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x65,
|
0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c,
|
||||||
0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01,
|
0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20,
|
||||||
0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3a, 0x0a, 0x07, 0x61, 0x63, 0x63,
|
0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3a, 0x0a, 0x07, 0x61, 0x63,
|
||||||
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
|
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72,
|
||||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e,
|
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c,
|
||||||
0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x61, 0x63,
|
0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x61,
|
||||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
|
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x70, 0x73, 0x18, 0x04, 0x20,
|
||||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
0x01, 0x28, 0x0d, 0x52, 0x03, 0x69, 0x70, 0x73, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e,
|
||||||
0x6c, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63,
|
0x6f, 0x63, 0x6f, 0x6c, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0xaa, 0x02,
|
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
|
||||||
0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f,
|
0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
||||||
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x6c, 0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
|
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -16,4 +16,7 @@ message User {
|
||||||
// Protocol specific account information. Must be the account proto in one of
|
// Protocol specific account information. Must be the account proto in one of
|
||||||
// the proxies.
|
// the proxies.
|
||||||
xray.common.serial.TypedMessage account = 3;
|
xray.common.serial.TypedMessage account = 3;
|
||||||
|
|
||||||
|
// Allowed IPs
|
||||||
|
uint32 ips = 4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,13 +231,15 @@ func (list *PortList) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
EmailString string `json:"email"`
|
EmailString string `json:"email"`
|
||||||
LevelByte byte `json:"level"`
|
LevelByte byte `json:"level"`
|
||||||
|
IpLimitByte byte `json:"ips"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *User) Build() *protocol.User {
|
func (v *User) Build() *protocol.User {
|
||||||
return &protocol.User{
|
return &protocol.User{
|
||||||
Email: v.EmailString,
|
Email: v.EmailString,
|
||||||
Level: uint32(v.LevelByte),
|
Level: uint32(v.LevelByte),
|
||||||
|
Ips: uint32(v.IpLimitByte),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ type hasHandshakeAddress interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process implements proxy.Inbound.
|
// Process implements proxy.Inbound.
|
||||||
func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
|
func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher, _ *map[session.ID]*stat.UserIpRestriction, _ *stat.UserIpRestriction) error {
|
||||||
newError("processing connection from: ", conn.RemoteAddr()).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
newError("processing connection from: ", conn.RemoteAddr()).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||||
dest := net.Destination{
|
dest := net.Destination{
|
||||||
Network: network,
|
Network: network,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
@ -22,7 +23,7 @@ type Inbound interface {
|
||||||
Network() []net.Network
|
Network() []net.Network
|
||||||
|
|
||||||
// Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound.
|
// Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound.
|
||||||
Process(context.Context, net.Network, stat.Connection, routing.Dispatcher) error
|
Process(context.Context, net.Network, stat.Connection, routing.Dispatcher, *map[session.ID]*stat.UserIpRestriction, *stat.UserIpRestriction) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Outbound process outbound connections.
|
// An Outbound process outbound connections.
|
||||||
|
|
|
@ -178,7 +178,7 @@ func (*Handler) Network() []net.Network {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process implements proxy.Inbound.Process().
|
// Process implements proxy.Inbound.Process().
|
||||||
func (h *Handler) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error {
|
func (h *Handler) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher, usrIpRstrct *map[session.ID]*stat.UserIpRestriction, connIp *stat.UserIpRestriction) error {
|
||||||
sid := session.ExportIDToError(ctx)
|
sid := session.ExportIDToError(ctx)
|
||||||
|
|
||||||
iConn := connection
|
iConn := connection
|
||||||
|
@ -447,6 +447,27 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||||
// Flow: requestAddons.Flow,
|
// Flow: requestAddons.Flow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.User.IpLimit > 0) {
|
||||||
|
addr := connection.RemoteAddr().(*net.TCPAddr)
|
||||||
|
user := account.ID.String()
|
||||||
|
|
||||||
|
uniqueIps := make(map[string]bool)
|
||||||
|
// Iterate through the connections and find unique used IP addresses withing last 30 seconds.
|
||||||
|
for _, conn := range *usrIpRstrct {
|
||||||
|
if conn.User == user && !conn.IpAddress.Equal(addr.IP) && ((time.Now().Unix() - conn.Time) < 30) {
|
||||||
|
uniqueIps[conn.IpAddress.String()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(uniqueIps) >= int(request.User.IpLimit)) {
|
||||||
|
return newError("User ", user, " has exceeded their allowed IPs.").AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
connIp.IpAddress = addr.IP
|
||||||
|
connIp.User = user
|
||||||
|
connIp.Time = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
var netConn net.Conn
|
var netConn net.Conn
|
||||||
var rawConn syscall.RawConn
|
var rawConn syscall.RawConn
|
||||||
var input *bytes.Reader
|
var input *bytes.Reader
|
||||||
|
|
|
@ -6,6 +6,12 @@ import (
|
||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UserIpRestriction struct {
|
||||||
|
User string
|
||||||
|
IpAddress net.IP
|
||||||
|
Time int64
|
||||||
|
}
|
||||||
|
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
net.Conn
|
net.Conn
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue