feat: support radius accounting request (#2362)

* feat: add radius server

* feat: parse org from packet

* feat: add comment

* feat: support radius accounting

* feat: change log

* feat: add copyright
This commit is contained in:
haiwu
2023-09-26 22:48:00 +08:00
committed by GitHub
parent 981908b0b6
commit 6fe5c44c1c
6 changed files with 229 additions and 38 deletions

View File

@ -15,24 +15,27 @@
package radius
import (
"fmt"
"log"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
"layeh.com/radius/rfc2866"
)
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a#tab_3
func StartRadiusServer() {
secret := conf.GetConfigString("radiusSecret")
server := radius.PacketServer{
Addr: "0.0.0.0:" + conf.GetConfigString("radiusServerPort"),
Handler: radius.HandlerFunc(handlerRadius),
SecretSource: radius.StaticSecretSource([]byte(`secret`)),
SecretSource: radius.StaticSecretSource([]byte(secret)),
}
log.Printf("Starting Radius server on %s", server.Addr)
if err := server.ListenAndServe(); err != nil {
log.Printf("StartRadiusServer() failed, err = %s", err.Error())
log.Printf("StartRadiusServer() failed, err = %v", err)
}
}
@ -40,6 +43,8 @@ func handlerRadius(w radius.ResponseWriter, r *radius.Request) {
switch r.Code {
case radius.CodeAccessRequest:
handleAccessRequest(w, r)
case radius.CodeAccountingRequest:
handleAccountingRequest(w, r)
default:
log.Printf("radius message, code = %d", r.Code)
}
@ -48,20 +53,57 @@ func handlerRadius(w radius.ResponseWriter, r *radius.Request) {
func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
username := rfc2865.UserName_GetString(r.Packet)
password := rfc2865.UserPassword_GetString(r.Packet)
organization := parseOrganization(r.Packet)
code := radius.CodeAccessAccept
log.Printf("username=%v, password=%v, code=%v, org=%v", username, password, code, organization)
organization := rfc2865.Class_GetString(r.Packet)
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
if organization == "" {
code = radius.CodeAccessReject
w.Write(r.Response(code))
w.Write(r.Response(radius.CodeAccessReject))
return
}
_, msg := object.CheckUserPassword(organization, username, password, "en")
if msg != "" {
code = radius.CodeAccessReject
w.Write(r.Response(code))
w.Write(r.Response(radius.CodeAccessReject))
return
}
w.Write(r.Response(code))
w.Write(r.Response(radius.CodeAccessAccept))
}
func handleAccountingRequest(w radius.ResponseWriter, r *radius.Request) {
statusType := rfc2866.AcctStatusType_Get(r.Packet)
username := rfc2865.UserName_GetString(r.Packet)
organization := rfc2865.Class_GetString(r.Packet)
log.Printf("handleAccountingRequest() username=%v, org=%v, statusType=%v", username, organization, statusType)
w.Write(r.Response(radius.CodeAccountingResponse))
var err error
defer func() {
if err != nil {
log.Printf("handleAccountingRequest() failed, err = %v", err)
}
}()
switch statusType {
case rfc2866.AcctStatusType_Value_Start:
// Start an accounting session
ra := GetAccountingFromRequest(r)
err = object.AddRadiusAccounting(ra)
case rfc2866.AcctStatusType_Value_InterimUpdate, rfc2866.AcctStatusType_Value_Stop:
// Interim update to an accounting session | Stop an accounting session
var (
newRa = GetAccountingFromRequest(r)
oldRa *object.RadiusAccounting
)
oldRa, err = object.GetRadiusAccountingBySessionId(newRa.AcctSessionId)
if err != nil {
return
}
if oldRa == nil {
if err = object.AddRadiusAccounting(newRa); err != nil {
return
}
}
stop := statusType == rfc2866.AcctStatusType_Value_Stop
err = object.InterimUpdateRadiusAccounting(oldRa, newRa, stop)
case rfc2866.AcctStatusType_Value_AccountingOn, rfc2866.AcctStatusType_Value_AccountingOff:
// By default, no Accounting-On or Accounting-Off messages are sent (no acct-on-off).
default:
err = fmt.Errorf("unsupport statusType = %v", statusType)
}
}

View File

@ -29,11 +29,7 @@ func TestAccessRequestRejected(t *testing.T) {
packet := radius.New(radius.CodeAccessRequest, []byte(`secret`))
rfc2865.UserName_SetString(packet, "admin")
rfc2865.UserPassword_SetString(packet, "12345")
vsa, err := radius.NewVendorSpecific(OrganizationVendorID, []byte("built-in"))
if err != nil {
t.Fatal(err)
}
packet.Add(rfc2865.VendorSpecific_Type, vsa)
rfc2865.Class_SetString(packet, "built-in")
response, err := radius.Exchange(context.Background(), packet, "localhost:1812")
if err != nil {
t.Fatal(err)
@ -47,11 +43,7 @@ func TestAccessRequestAccepted(t *testing.T) {
packet := radius.New(radius.CodeAccessRequest, []byte(`secret`))
rfc2865.UserName_SetString(packet, "admin")
rfc2865.UserPassword_SetString(packet, "123")
vsa, err := radius.NewVendorSpecific(OrganizationVendorID, []byte("built-in"))
if err != nil {
t.Fatal(err)
}
packet.Add(rfc2865.VendorSpecific_Type, vsa)
rfc2865.Class_SetString(packet, "built-in")
response, err := radius.Exchange(context.Background(), packet, "localhost:1812")
if err != nil {
t.Fatal(err)

View File

@ -15,26 +15,53 @@
package radius
import (
"fmt"
"time"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
"layeh.com/radius/rfc2866"
"layeh.com/radius/rfc2869"
)
const (
OrganizationVendorID = uint32(100)
)
func parseOrganization(p *radius.Packet) string {
for _, avp := range p.Attributes {
if avp.Type == rfc2865.VendorSpecific_Type {
attr := avp.Attribute
vendorId, value, err := radius.VendorSpecific(attr)
if err != nil {
return ""
}
if vendorId == OrganizationVendorID {
return string(value)
}
}
func GetAccountingFromRequest(r *radius.Request) *object.RadiusAccounting {
acctInputOctets := int(rfc2866.AcctInputOctets_Get(r.Packet))
acctInputGigawords := int(rfc2869.AcctInputGigawords_Get(r.Packet))
acctOutputOctets := int(rfc2866.AcctOutputOctets_Get(r.Packet))
acctOutputGigawords := int(rfc2869.AcctOutputGigawords_Get(r.Packet))
organization := rfc2865.Class_GetString(r.Packet)
getAcctStartTime := func(sessionTime int) time.Time {
m, _ := time.ParseDuration(fmt.Sprintf("-%ds", sessionTime))
return time.Now().Add(m)
}
return ""
ra := &object.RadiusAccounting{
Owner: organization,
Name: "ra_" + util.GenerateId()[:6],
CreatedTime: time.Now(),
Username: rfc2865.UserName_GetString(r.Packet),
ServiceType: int64(rfc2865.ServiceType_Get(r.Packet)),
NasId: rfc2865.NASIdentifier_GetString(r.Packet),
NasIpAddr: rfc2865.NASIPAddress_Get(r.Packet).String(),
NasPortId: rfc2869.NASPortID_GetString(r.Packet),
NasPortType: int64(rfc2865.NASPortType_Get(r.Packet)),
NasPort: int64(rfc2865.NASPort_Get(r.Packet)),
FramedIpAddr: rfc2865.FramedIPAddress_Get(r.Packet).String(),
FramedIpNetmask: rfc2865.FramedIPNetmask_Get(r.Packet).String(),
AcctSessionId: rfc2866.AcctSessionID_GetString(r.Packet),
AcctSessionTime: int64(rfc2866.AcctSessionTime_Get(r.Packet)),
AcctInputTotal: int64(acctInputOctets) + int64(acctInputGigawords)*4*1024*1024*1024,
AcctOutputTotal: int64(acctOutputOctets) + int64(acctOutputGigawords)*4*1024*1024*1024,
AcctInputPackets: int64(rfc2866.AcctInputPackets_Get(r.Packet)),
AcctOutputPackets: int64(rfc2866.AcctInputPackets_Get(r.Packet)),
AcctStartTime: getAcctStartTime(int(rfc2866.AcctSessionTime_Get(r.Packet))),
AcctTerminateCause: int64(rfc2866.AcctTerminateCause_Get(r.Packet)),
LastUpdate: time.Now(),
}
return ra
}