From 4cbad26d091ae78c9dd23084e37d7d6a8af43cef Mon Sep 17 00:00:00 2001 From: CrazyBolillo Date: Sun, 29 Sep 2024 00:38:54 -0600 Subject: [PATCH] feat(endpoint): nat & media_encryption attributes A new NAT parameter can be set on endpoints. It abstracts away the necessary modifications to endpoint fields required for a far end NAT phone to work with asterisk. Support for setting media_encryption attributes on endpoints was also added, however DTLS needs more configuration which so far has not been implemented so in practical terms only sdes will work through eryth. Closes #26. Related to #23. --- docs/swagger.yaml | 12 +++++ internal/handler/endpoint_test.go | 8 +++ internal/model/endpoint.go | 6 +++ internal/service/endpoint.go | 56 +++++++++++++++++--- internal/sqlc/queries.sql.go | 87 ++++++++++++++++++++++--------- queries.sql | 37 +++++++++++-- 6 files changed, 171 insertions(+), 35 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index b568625..ade1e9c 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -27,12 +27,16 @@ definitions: type: string displayName: type: string + encryption: + type: string extension: type: string id: type: string maxContacts: type: integer + nat: + type: boolean sid: type: integer transport: @@ -74,12 +78,16 @@ definitions: type: string displayName: type: string + encryption: + type: string extension: type: string id: type: string maxContacts: type: integer + nat: + type: boolean password: type: string transport: @@ -95,10 +103,14 @@ definitions: type: string displayName: type: string + encryption: + type: string extension: type: string maxContacts: type: integer + nat: + type: boolean password: type: string transport: diff --git a/internal/handler/endpoint_test.go b/internal/handler/endpoint_test.go index d376d9c..5fc98f4 100644 --- a/internal/handler/endpoint_test.go +++ b/internal/handler/endpoint_test.go @@ -110,6 +110,7 @@ func MustCreate(handler http.Handler) func(*testing.T) { Extension: "1234", DisplayName: "Zinnia Elegans", MaxContacts: 10, + Nat: true, } res := createEndpoint(t, handler, endpoint) if res.Code != http.StatusCreated { @@ -127,6 +128,7 @@ func MustCreate(handler http.Handler) func(*testing.T) { Codecs: endpoint.Codecs, MaxContacts: 10, Extension: "1234", + Nat: true, } if !reflect.DeepEqual(got, want) { @@ -198,15 +200,21 @@ func MustUpdate(handler http.Handler) func(*testing.T) { Codecs: []string{"ulaw", "opus"}, Extension: "5061", DisplayName: "Big Chungus", + Nat: false, + Encryption: "dtls", } res := createEndpoint(t, handler, endpoint) want := parseEndpoint(t, res.Body) want.MaxContacts = 5 want.Extension = "6072" + want.Nat = true + want.Encryption = "sdes" res = updateEndpoint(t, handler, want.Sid, model.PatchedEndpoint{ MaxContacts: &want.MaxContacts, Extension: &want.Extension, + Nat: &want.Nat, + Encryption: &want.Encryption, }) if res.Code != http.StatusOK { t.Errorf("invalid http code, got %d, want %d", res.Code, http.StatusOK) diff --git a/internal/model/endpoint.go b/internal/model/endpoint.go index 5e3cf05..628e42a 100644 --- a/internal/model/endpoint.go +++ b/internal/model/endpoint.go @@ -10,6 +10,8 @@ type Endpoint struct { Codecs []string `json:"codecs"` MaxContacts int32 `json:"maxContacts"` Extension string `json:"extension"` + Nat bool `json:"nat"` + Encryption string `json:"encryption"` } type NewEndpoint struct { @@ -22,6 +24,8 @@ type NewEndpoint struct { MaxContacts int32 `json:"maxContacts"` Extension string `json:"extension"` DisplayName string `json:"displayName"` + Nat bool `json:"nat"` + Encryption string `json:"encryption"` } type PatchedEndpoint struct { @@ -32,6 +36,8 @@ type PatchedEndpoint struct { Codecs []string `json:"codecs,"` MaxContacts *int32 `json:"maxContacts,"` Extension *string `json:"extension,"` + Nat *bool `json:"nat,"` + Encryption *string `json:"encryption,"` } type EndpointPageEntry struct { diff --git a/internal/service/endpoint.go b/internal/service/endpoint.go index af480d6..09ead7a 100644 --- a/internal/service/endpoint.go +++ b/internal/service/endpoint.go @@ -44,6 +44,22 @@ func displayNameFromClid(callerID string) string { return callerID[1:end] } +func parseMediaEncryption(value string) (sqlc.NullPjsipMediaEncryptionValues, error) { + switch value { + case "sdes": + fallthrough + case "dtls": + return sqlc.NullPjsipMediaEncryptionValues{ + PjsipMediaEncryptionValues: sqlc.PjsipMediaEncryptionValues(value), + Valid: true, + }, nil + case "": + return sqlc.NullPjsipMediaEncryptionValues{}, nil + default: + return sqlc.NullPjsipMediaEncryptionValues{}, fmt.Errorf("invalid value for media_encryption '%s'", value) + } +} + func (e *EndpointService) Create(ctx context.Context, payload model.NewEndpoint) (model.Endpoint, error) { tx, err := e.Begin(ctx) if err != nil { @@ -62,13 +78,25 @@ func (e *EndpointService) Create(ctx context.Context, payload model.NewEndpoint) return model.Endpoint{}, err } + natValue := sqlc.NullAstBoolValues{AstBoolValues: "no", Valid: true} + if payload.Nat { + natValue.AstBoolValues = "yes" + } + + mediaValue, err := parseMediaEncryption(payload.Encryption) + if err != nil { + return model.Endpoint{}, err + } + sid, err := queries.NewEndpoint(ctx, sqlc.NewEndpointParams{ - ID: payload.ID, - Accountcode: db.Text(cmp.Or(payload.AccountCode, payload.ID)), - Transport: db.Text(payload.Transport), - Context: db.Text(payload.Context), - Allow: db.Text(strings.Join(payload.Codecs, ",")), - Callerid: db.Text(fmt.Sprintf(`"%s" <%s>`, payload.DisplayName, payload.ID)), + ID: payload.ID, + Accountcode: db.Text(cmp.Or(payload.AccountCode, payload.ID)), + Transport: db.Text(payload.Transport), + Context: db.Text(payload.Context), + Allow: db.Text(strings.Join(payload.Codecs, ",")), + Callerid: db.Text(fmt.Sprintf(`"%s" <%s>`, payload.DisplayName, payload.ID)), + Nat: natValue, + MediaEncryption: mediaValue, }) if err != nil { return model.Endpoint{}, err @@ -117,6 +145,8 @@ func (e *EndpointService) Read(ctx context.Context, sid int32) (model.Endpoint, Codecs: strings.Split(row.Allow.String, ","), MaxContacts: row.MaxContacts.Int32, Extension: row.Extension.String, + Nat: row.Nat.Bool, + Encryption: string(row.MediaEncryption.PjsipMediaEncryptionValues), } return endpoint, nil } @@ -159,6 +189,20 @@ func (e *EndpointService) Update(ctx context.Context, sid int32, payload model.P } else { patchedEndpoint.Allow = endpoint.Allow } + if payload.Nat != nil { + patchedEndpoint.Nat = sqlc.NullAstBoolValues{AstBoolValues: "yes", Valid: *payload.Nat} + } else { + patchedEndpoint.Nat = sqlc.NullAstBoolValues{AstBoolValues: "yes", Valid: endpoint.Nat.Bool} + } + if payload.Encryption != nil { + patchedEndpoint.MediaEncryption, err = parseMediaEncryption(*payload.Encryption) + if err != nil { + return model.Endpoint{}, err + } + } else { + patchedEndpoint.MediaEncryption = endpoint.MediaEncryption + } + err = queries.UpdateEndpointBySid(ctx, patchedEndpoint) if err != nil { return model.Endpoint{}, err diff --git a/internal/sqlc/queries.sql.go b/internal/sqlc/queries.sql.go index a34dcbc..8533135 100644 --- a/internal/sqlc/queries.sql.go +++ b/internal/sqlc/queries.sql.go @@ -83,7 +83,16 @@ func (q *Queries) GetEndpointByExtension(ctx context.Context, arg GetEndpointByE const getEndpointByID = `-- name: GetEndpointByID :one SELECT - pe.id, pe.accountcode, pe.callerid, pe.context, ee.extension, pe.transport, aor.max_contacts, pe.allow + pe.id, + pe.accountcode, + pe.callerid, + pe.context, + ee.extension, + pe.transport, + aor.max_contacts, + pe.allow, + (pe.force_rport::text::boolean AND pe.rewrite_contact::text::boolean AND pe.rtp_symmetric::text::boolean) AS nat, + pe.media_encryption FROM ps_endpoints pe INNER JOIN @@ -95,14 +104,16 @@ WHERE ` type GetEndpointByIDRow struct { - ID string `json:"id"` - Accountcode pgtype.Text `json:"accountcode"` - Callerid pgtype.Text `json:"callerid"` - Context pgtype.Text `json:"context"` - Extension pgtype.Text `json:"extension"` - Transport pgtype.Text `json:"transport"` - MaxContacts pgtype.Int4 `json:"max_contacts"` - Allow pgtype.Text `json:"allow"` + ID string `json:"id"` + Accountcode pgtype.Text `json:"accountcode"` + Callerid pgtype.Text `json:"callerid"` + Context pgtype.Text `json:"context"` + Extension pgtype.Text `json:"extension"` + Transport pgtype.Text `json:"transport"` + MaxContacts pgtype.Int4 `json:"max_contacts"` + Allow pgtype.Text `json:"allow"` + Nat pgtype.Bool `json:"nat"` + MediaEncryption NullPjsipMediaEncryptionValues `json:"media_encryption"` } func (q *Queries) GetEndpointByID(ctx context.Context, sid int32) (GetEndpointByIDRow, error) { @@ -117,6 +128,8 @@ func (q *Queries) GetEndpointByID(ctx context.Context, sid int32) (GetEndpointBy &i.Transport, &i.MaxContacts, &i.Allow, + &i.Nat, + &i.MediaEncryption, ) return i, err } @@ -190,19 +203,35 @@ func (q *Queries) NewAOR(ctx context.Context, arg NewAORParams) error { const newEndpoint = `-- name: NewEndpoint :one INSERT INTO ps_endpoints - (id, transport, aors, auth, context, disallow, allow, callerid, accountcode) + ( + id, + transport, + aors, + auth, + context, + disallow, + allow, + callerid, + accountcode, + force_rport, + rewrite_contact, + rtp_symmetric, + media_encryption + ) VALUES - ($1, $2, $1, $1, $3, 'all', $4, $5, $6) + ($1, $2, $1, $1, $3, 'all', $4, $5, $6, $8, $8, $8, $7) RETURNING sid ` type NewEndpointParams struct { - ID string `json:"id"` - Transport pgtype.Text `json:"transport"` - Context pgtype.Text `json:"context"` - Allow pgtype.Text `json:"allow"` - Callerid pgtype.Text `json:"callerid"` - Accountcode pgtype.Text `json:"accountcode"` + ID string `json:"id"` + Transport pgtype.Text `json:"transport"` + Context pgtype.Text `json:"context"` + Allow pgtype.Text `json:"allow"` + Callerid pgtype.Text `json:"callerid"` + Accountcode pgtype.Text `json:"accountcode"` + MediaEncryption NullPjsipMediaEncryptionValues `json:"media_encryption"` + Nat NullAstBoolValues `json:"nat"` } func (q *Queries) NewEndpoint(ctx context.Context, arg NewEndpointParams) (int32, error) { @@ -213,6 +242,8 @@ func (q *Queries) NewEndpoint(ctx context.Context, arg NewEndpointParams) (int32 arg.Allow, arg.Callerid, arg.Accountcode, + arg.MediaEncryption, + arg.Nat, ) var sid int32 err := row.Scan(&sid) @@ -286,17 +317,23 @@ SET callerid = $1, context = $2, transport = $3, - allow = $4 + allow = $4, + force_rport = $7, + rewrite_contact = $7, + rtp_symmetric = $7, + media_encryption = $5 WHERE - sid = $5 + sid = $6 ` type UpdateEndpointBySidParams struct { - Callerid pgtype.Text `json:"callerid"` - Context pgtype.Text `json:"context"` - Transport pgtype.Text `json:"transport"` - Allow pgtype.Text `json:"allow"` - Sid int32 `json:"sid"` + Callerid pgtype.Text `json:"callerid"` + Context pgtype.Text `json:"context"` + Transport pgtype.Text `json:"transport"` + Allow pgtype.Text `json:"allow"` + MediaEncryption NullPjsipMediaEncryptionValues `json:"media_encryption"` + Sid int32 `json:"sid"` + Nat NullAstBoolValues `json:"nat"` } func (q *Queries) UpdateEndpointBySid(ctx context.Context, arg UpdateEndpointBySidParams) error { @@ -305,7 +342,9 @@ func (q *Queries) UpdateEndpointBySid(ctx context.Context, arg UpdateEndpointByS arg.Context, arg.Transport, arg.Allow, + arg.MediaEncryption, arg.Sid, + arg.Nat, ) return err } diff --git a/queries.sql b/queries.sql index a638f58..53d0e40 100644 --- a/queries.sql +++ b/queries.sql @@ -12,9 +12,23 @@ VALUES -- name: NewEndpoint :one INSERT INTO ps_endpoints - (id, transport, aors, auth, context, disallow, allow, callerid, accountcode) + ( + id, + transport, + aors, + auth, + context, + disallow, + allow, + callerid, + accountcode, + force_rport, + rewrite_contact, + rtp_symmetric, + media_encryption + ) VALUES - ($1, $2, $1, $1, $3, 'all', $4, $5, $6) + ($1, $2, $1, $1, $3, 'all', $4, $5, $6, @nat, @nat, @nat, $7) RETURNING sid; -- name: DeleteEndpoint :one @@ -56,7 +70,16 @@ WHERE -- name: GetEndpointByID :one SELECT - pe.id, pe.accountcode, pe.callerid, pe.context, ee.extension, pe.transport, aor.max_contacts, pe.allow + pe.id, + pe.accountcode, + pe.callerid, + pe.context, + ee.extension, + pe.transport, + aor.max_contacts, + pe.allow, + (pe.force_rport::text::boolean AND pe.rewrite_contact::text::boolean AND pe.rtp_symmetric::text::boolean) AS nat, + pe.media_encryption FROM ps_endpoints pe INNER JOIN @@ -76,9 +99,13 @@ SET callerid = $1, context = $2, transport = $3, - allow = $4 + allow = $4, + force_rport = @nat, + rewrite_contact = @nat, + rtp_symmetric = @nat, + media_encryption = $5 WHERE - sid = $5; + sid = $6; -- name: UpdateExtensionByEndpointId :exec UPDATE