Fix client ip by get client ip base on username hash

Count username hash, get client ip base on it, it will make ip
fixed base on client login username.
This commit is contained in:
lucheng
2024-08-09 12:30:26 +08:00
parent b6c51a357c
commit a6bfacb0c2
5 changed files with 193 additions and 4 deletions

View File

@@ -175,14 +175,14 @@ func (svc *Server) OfferIPToClient(conn *net.UDPConn, ip string, raddr *net.UDPA
return nil return nil
} }
func (svc *Server) CreateClientForAddr(addr *net.UDPAddr, conn *net.UDPConn) (*UClient, error) { func (svc *Server) CreateClientForAddr(addr *net.UDPAddr, conn *net.UDPConn, username string) (*UClient, error) {
iface, err := utils.NewTap(svc.Bridge) iface, err := utils.NewTap(svc.Bridge)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Pop a ip for client // Pop a ip for client
ip, err := svc.PopIPFromPool() ip, err := svc.IPForUser(username)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -199,6 +199,8 @@ func (svc *Server) CreateClientForAddr(addr *net.UDPAddr, conn *net.UDPConn) (*U
client.Svc = svc client.Svc = svc
client.IP = ip client.IP = ip
log.Infof("new client remote addr %s ip %s login at %s\n", client.RAddr.String(), client.IP.String(), client.Login)
UPool[addr.String()] = client UPool[addr.String()] = client
// Monitor client heartbeat // Monitor client heartbeat
@@ -276,7 +278,7 @@ func (svc *Server) ListenAndServe() error {
log.Infof("client %s login to %s succeed\n", addr.String(), u) log.Infof("client %s login to %s succeed\n", addr.String(), u)
// Create client for authed addr // Create client for authed addr
client, err := svc.CreateClientForAddr(addr, ln) client, err := svc.CreateClientForAddr(addr, ln, u)
if err != nil { if err != nil {
log.Errorf("create authed client %s\n", err.Error()) log.Errorf("create authed client %s\n", err.Error())
svc.SendResponse(ln, packet.RSP_INTERNAL_ERR, addr) svc.SendResponse(ln, packet.RSP_INTERNAL_ERR, addr)

View File

@@ -57,7 +57,43 @@ func (svc *Server) IdxFromIP(ip net.IP) int {
return int(ipInt - ipStartInt) return int(ipInt - ipStartInt)
} }
// TODO(shawnlu): Implement it with hash map, username as input to count hash func (svc *Server) IPForUser(username string) (net.IP, error) {
idx := utils.IdxFromString(svc.IPCount, username)
idxEnd := idx - 1
for idx < svc.IPCount {
if idx == idxEnd {
// Checked the last idx
if svc.IPIdxInPool(idx) {
break
}
svc.MLock.Lock()
svc.UsedIP = append(svc.UsedIP, idx)
svc.MLock.Unlock()
return svc.IPFromIdx(idx), nil
}
if svc.IPIdxInPool(idx) {
idx += 1
if idx == svc.IPCount {
// Check from zero
idx = 0
}
continue
}
svc.MLock.Lock()
svc.UsedIP = append(svc.UsedIP, idx)
svc.MLock.Unlock()
return svc.IPFromIdx(idx), nil
}
return nil, errors.New("run out of ip")
}
func (svc *Server) PopIPFromPool() (net.IP, error) { func (svc *Server) PopIPFromPool() (net.IP, error) {
for idx := 0; idx < svc.IPCount; idx++ { for idx := 0; idx < svc.IPCount; idx++ {
if svc.IPIdxInPool(idx) { if svc.IPIdxInPool(idx) {

View File

@@ -0,0 +1,84 @@
package server
import (
"net"
"reflect"
"sync"
"testing"
"bou.ke/monkey"
"github.com/lucheng0127/virtuallan/pkg/utils"
)
func TestServer_IPForUser(t *testing.T) {
type args struct {
username string
}
tests := []struct {
name string
args args
want net.IP
wantErr bool
patchFunc interface{}
targetFunc interface{}
}{
{
name: "idx 1-1",
args: args{username: "whocares"},
want: net.ParseIP("192.168.123.101").To4(),
wantErr: false,
patchFunc: utils.IdxFromString,
targetFunc: func(int, string) int { return 1 },
},
{
name: "idx 1-2",
args: args{username: "whocares"},
want: net.ParseIP("192.168.123.102").To4(),
wantErr: false,
patchFunc: utils.IdxFromString,
targetFunc: func(int, string) int { return 1 },
},
{
name: "idx 1-3",
args: args{username: "whocares"},
want: net.ParseIP("192.168.123.100").To4(),
wantErr: false,
patchFunc: utils.IdxFromString,
targetFunc: func(int, string) int { return 1 },
},
{
name: "idx 1-4",
args: args{username: "whocares"},
want: nil,
wantErr: true,
patchFunc: utils.IdxFromString,
targetFunc: func(int, string) int { return 1 },
},
}
ipStart := net.ParseIP("192.168.123.100").To4()
svc := &Server{
UsedIP: make([]int, 0),
IPStart: ipStart,
IPCount: 3,
MLock: sync.Mutex{},
Routes: make(map[string]string),
}
for _, tt := range tests {
if tt.targetFunc != nil {
monkey.Patch(tt.patchFunc, tt.targetFunc)
}
t.Run(tt.name, func(t *testing.T) {
got, err := svc.IPForUser(tt.args.username)
if (err != nil) != tt.wantErr {
t.Errorf("Server.IPForUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Server.IPForUser() = %v, want %v", got, tt.want)
}
})
}
}

12
pkg/utils/hash.go Normal file
View File

@@ -0,0 +1,12 @@
package utils
import (
"hash/fnv"
)
func IdxFromString(step int, str string) int {
h := fnv.New32a()
h.Write([]byte(str))
return int(h.Sum32() % uint32(step))
}

55
pkg/utils/hash_test.go Normal file
View File

@@ -0,0 +1,55 @@
package utils
import "testing"
func TestIdxFromString(t *testing.T) {
type args struct {
step int
str string
}
tests := []struct {
name string
args args
want int
}{
{
name: "step 100 user1",
want: 32,
args: args{
step: 100,
str: "shawn",
},
},
{
name: "step 100 user2",
want: 59,
args: args{
step: 100,
str: "guest",
},
},
{
name: "step 30 user1",
want: 2,
args: args{
step: 30,
str: "shawn",
},
},
{
name: "step 30 user2",
want: 9,
args: args{
step: 30,
str: "guest",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IdxFromString(tt.args.step, tt.args.str); got != tt.want {
t.Errorf("IdxFromString() = %v, want %v", got, tt.want)
}
})
}
}