!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:
Devman 2023-06-30 13:13:36 +00:00
parent 34b68518fd
commit 3d692eb208
9 changed files with 94 additions and 22 deletions

View file

@ -42,6 +42,7 @@ type tcpWorker struct {
sniffingConfig *proxyman.SniffingConfig
uplinkCounter stats.Counter
downlinkCounter stats.Counter
ipLimitPool map[session.ID]*stat.UserIpRestriction
hub internet.Listener
@ -104,9 +105,18 @@ func (w *tcpWorker) callback(conn stat.Connection) {
}
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))
}
// Deletes the IP address from the pool after the connection ends
delete(w.ipLimitPool, sid)
cancel()
conn.Close()
}
@ -116,6 +126,9 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
}
func (w *tcpWorker) Start() error {
if len(w.ipLimitPool) == 0 {
w.ipLimitPool = make(map[session.ID]*stat.UserIpRestriction)
}
ctx := context.Background()
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
go w.callback(conn)
@ -244,6 +257,7 @@ type udpWorker struct {
sniffingConfig *proxyman.SniffingConfig
uplinkCounter stats.Counter
downlinkCounter stats.Counter
ipLimitPool map[session.ID]*stat.UserIpRestriction
checker *task.Periodic
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
}
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))
}
// Deletes the IP address from the pool after the connection ends
delete(w.ipLimitPool, sid)
conn.Close()
// conn not removed by checker TODO may be lock worker here is better
if !conn.inactive {
@ -379,6 +403,9 @@ func (w *udpWorker) clean() 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)
ctx := context.Background()
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)
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))
}
cancel()

View file

@ -27,6 +27,7 @@ func (u *User) ToMemoryUser() (*MemoryUser, error) {
Account: account,
Email: u.Email,
Level: u.Level,
IpLimit: u.Ips,
}, nil
}
@ -36,4 +37,5 @@ type MemoryUser struct {
Account Account
Email string
Level uint32
IpLimit uint32
}

View file

@ -32,6 +32,8 @@ type User struct {
// Protocol specific account information. Must be the account proto in one of
// the proxies.
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() {
@ -87,6 +89,13 @@ func (x *User) GetAccount() *serial.TypedMessage {
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_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,
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,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6e, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14, 0x0a,
0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x65,
0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3a, 0x0a, 0x07, 0x61, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e,
0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
0x6c, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 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,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x80, 0x01, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14,
0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c,
0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3a, 0x0a, 0x07, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c,
0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x61,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x70, 0x73, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x03, 0x69, 0x70, 0x73, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x63, 0x6f, 0x6c, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
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 (

View file

@ -16,4 +16,7 @@ message User {
// Protocol specific account information. Must be the account proto in one of
// the proxies.
xray.common.serial.TypedMessage account = 3;
// Allowed IPs
uint32 ips = 4;
}

View file

@ -231,13 +231,15 @@ func (list *PortList) UnmarshalJSON(data []byte) error {
}
type User struct {
EmailString string `json:"email"`
LevelByte byte `json:"level"`
EmailString string `json:"email"`
LevelByte byte `json:"level"`
IpLimitByte byte `json:"ips"`
}
func (v *User) Build() *protocol.User {
return &protocol.User{
Email: v.EmailString,
Level: uint32(v.LevelByte),
Ips: uint32(v.IpLimitByte),
}
}

View file

@ -76,7 +76,7 @@ type hasHandshakeAddress interface {
}
// 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))
dest := net.Destination{
Network: network,

View file

@ -10,6 +10,7 @@ import (
"github.com/xtls/xray-core/common/net"
"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/transport"
"github.com/xtls/xray-core/transport/internet"
@ -22,7 +23,7 @@ type Inbound interface {
Network() []net.Network
// 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.

View file

@ -178,7 +178,7 @@ func (*Handler) Network() []net.Network {
}
// 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)
iConn := connection
@ -447,6 +447,27 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
// 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 rawConn syscall.RawConn
var input *bytes.Reader

View file

@ -6,6 +6,12 @@ import (
"github.com/xtls/xray-core/features/stats"
)
type UserIpRestriction struct {
User string
IpAddress net.IP
Time int64
}
type Connection interface {
net.Conn
}