FreeSwitch 的 `mod_blacklist` 模块详解

FreeSwitch 的 mod_blacklist 模块详解

mod_blacklist 是 FreeSwitch 中用于实现黑名单/白名单功能的模块,可基于来电号码(Caller ID)、IP 地址或其他条件拦截或放行呼叫。结合 LuaJIT 脚本,能实现动态、高效的呼叫控制逻辑。


1. 核心功能

  • 黑白名单控制
    • 黑名单(Blacklist):直接拒绝特定号码或 IP 的呼叫。
    • 白名单(Whitelist):仅允许特定号码或 IP 的呼叫(优先级高于黑名单)。
  • 拦截条件
    • 支持 Caller ID(主叫号码)、Destination Number(被叫号码)、IP 地址SIP 头 等字段。
    • 可配置自动拉黑机制(如:多次验证失败后自动加入黑名单)。
  • 动态管理:支持通过数据库、API 或 LuaJIT 脚本实时更新名单。

2. 配置方法

2.1 启用模块

在 FreeSwitch 配置文件(autoload_configs/modules.conf.xml)中启用模块:

xml 复制代码
<load module="mod_blacklist"/>
2.2 配置黑白名单
  • 静态配置 :在 vars.xmldialplan 中定义名单:

    xml 复制代码
    <!-- 黑名单 -->
    <X-PRE-PROCESS cmd="set" data="blacklist=^(恶意号码正则|192\.168\.1\.100)$"/>
    <!-- 白名单 -->
    <X-PRE-PROCESS cmd="set" data="whitelist=^(\+86139\d+|信任IP)$"/>
  • 自动拉黑:配置失败次数阈值和时间窗口(如 5 分钟内失败 3 次自动拉黑):

    xml 复制代码
    <param name="auto_ban_seconds" value="3600"/> <!-- 拉黑时长(秒) -->
    <param name="max_failed_attempts" value="3"/>  <!-- 最大失败次数 -->

3. 结合 LuaJIT 实现动态控制

3.1 基本流程

通过 LuaJIT 脚本在呼叫建立阶段(SESSION_INIT)检查黑名单:

  1. 从数据库/缓存(如 Redis)中动态查询号码或 IP。
  2. 调用 mod_blacklist 的 API 进行拦截或放行。
3.2 LuaJIT 脚本示例
lua 复制代码
-- blacklist_check.lua
local blacklist = require "mod_blacklist"

local caller_id = session:getVariable("caller_id_number")
local client_ip = session:getVariable("sip_remote_ip")

-- 检查黑名单
if blacklist.check(caller_id) or blacklist.check(client_ip) then
    session:answer()
    session:streamFile("ivr/access_denied.wav")
    session:hangup()
    return
end

-- 放行并继续路由
session:continue()
3.3 集成到拨号计划

dialplan 中调用 Lua 脚本:

xml 复制代码
<extension name="blacklist_check">
  <condition>
    <action application="lua" data="blacklist_check.lua"/>
  </condition>
</extension>

4. 高级用法

4.1 自动拉黑恶意IP
lua 复制代码
-- 失败次数统计(示例伪代码)
local max_attempts = 3
local ban_time = 3600
local key = "blacklist:" .. client_ip

-- 使用 Redis 统计失败次数
local redis = require "redis"
local conn = redis.connect("127.0.0.1", 6379)
local attempts = conn:incr(key)

if attempts >= max_attempts then
    conn:expire(key, ban_time)  -- 设置过期时间
    blacklist.add(client_ip)    -- 动态加入黑名单
end
4.2 白名单例外
lua 复制代码
local whitelist = {"+8613812345678", "192.168.1.1"}
if not (blacklist.in_whitelist(caller_id) or blacklist.in_whitelist(client_ip)) then
    -- 执行黑名单检查
end

5. 应用场景

  • 防骚扰/欺诈:拦截高频呼叫、虚假号码。
  • 安全防护 :阻止 SIP 扫描工具或 DoS 攻击(如 freeswitch-py 发起的恶意注册)。
  • 企业内控:限制非授权号码访问特定IVR菜单。

6. 注意事项

  • 性能:LuaJIT 的高效性适合高并发场景,但频繁访问外部数据库可能成为瓶颈。
  • 误拦截:建议结合人工审核或日志监控。
  • 版本兼容性 :FreeSwitch 1.10+ 对 mod_blacklist 支持较好,旧版本需测试。

通过 mod_blacklist 和 LuaJIT 的灵活组合,可构建强大的呼叫控制逻辑,显著提升 FreeSwitch 的安全性和可管理性。


被叫号码的黑名单管理


是的,mod_blacklist 可以结合 LuaJIT 脚本实现被叫号码(Destination Number)的黑名单管理 。虽然 mod_blacklist 默认功能更偏向主叫号码(Caller ID)或 IP 拦截,但通过灵活的脚本逻辑和拨号计划(Dialplan)的配合,完全能实现对被叫号码的拦截。以下是具体实现方法:


1. 核心思路

通过 LuaJIT 脚本 在呼叫路由阶段(dialplan)检查被叫号码(destination_number)是否在黑名单中:

  • 若被叫号码在黑名单中,直接拦截呼叫。
  • 若不在黑名单,则放行并继续路由。

2. 实现步骤

2.1 定义被叫号码黑名单

在 Lua 脚本中定义黑名单逻辑(支持动态加载或静态列表):

lua 复制代码
-- blacklist_destination.lua
local destination = session:getVariable("destination_number")  -- 获取被叫号码

-- 定义被叫号码黑名单(支持正则表达式或静态列表)
local blocked_numbers = {
    "^95013$",          -- 拦截 95013
    "^\\+?86(400|800)", -- 拦截 400/800 开头的号码(国际格式兼容)
    "^192\.168\.1\.",   -- 拦截特定 IP 发起的被叫请求(如 SIP 扫描工具)
}

-- 检查是否命中黑名单
for _, pattern in ipairs(blocked_numbers) do
    if string.match(destination, pattern) then
        session:answer()
        session:streamFile("ivr/blocked.wav")  -- 播放拦截提示音
        session:hangup("CALL_REJECTED")        -- 挂断并标记原因
        return
    end
end

-- 放行呼叫
session:continue()
2.2 集成到拨号计划(Dialplan)

在 FreeSwitch 的拨号计划(如 default/context)中添加拦截规则:

xml 复制代码
<extension name="destination_blacklist">
  <condition field="destination_number" expression="^.*$">  <!-- 匹配所有被叫号码 -->
    <action application="lua" data="blacklist_destination.lua"/>
  </condition>
</extension>
2.3 动态管理黑名单(可选)

若需动态更新黑名单(例如从数据库或 API 加载):

lua 复制代码
-- 从 Redis 获取实时黑名单
local redis = require "redis"
local conn = redis.connect("127.0.0.1", 6379)
local blocked_numbers = conn:smembers("blocked_destinations")  -- 假设使用集合存储

-- 检查被叫号码
if blocked_numbers then
    for _, number in ipairs(blocked_numbers) do
        if destination == number then
            session:hangup("CALL_REJECTED")
            return
        end
    end
end

3. 高级场景

3.1 按主叫+被叫组合拦截

若需拦截特定主叫拨打特定被叫号码(如防止内部号码拨打国际长途):

lua 复制代码
local caller = session:getVariable("caller_id_number")
local destination = session:getVariable("destination_number")

-- 定义规则:主叫 1001 不允许拨打 00 开头的号码
if caller == "1001" and string.match(destination, "^00") then
    session:hangup("CALL_REJECTED")
    return
end
3.2 基于计费系统的拦截

结合外部计费系统,检查账户余额是否允许拨打高价值号码:

lua 复制代码
local account = session:getVariable("user_account")
local destination = session:getVariable("destination_number")

-- 调用外部 API 检查权限
local http = require "socket.http"
local response = http.request("http://billing/api/check?account="..account.."&dest="..destination)

if response == "DENY" then
    session:streamFile("ivr/insufficient_balance.wav")
    session:hangup()
    return
end

4. 性能优化

  • 缓存黑名单:将黑名单缓存在 Lua 脚本的全局变量中,定期更新(如每 5 分钟从数据库加载一次),避免每次呼叫都查询外部存储。
  • 正则优化 :使用高效的正则表达式(如避免 .* 贪婪匹配),减少 CPU 开销。
  • 分层次拦截
    • IP 层拦截 :优先通过 mod_blacklist 的 IP 黑名单拦截恶意流量。
    • Lua 脚本拦截:再通过 Lua 脚本精细化处理被叫号码逻辑。

5. 注意事项

  1. 拨号计划顺序 :确保拦截逻辑的 extension 在拨号计划中优先级最高(先执行拦截检查,再处理正常路由)。

  2. 日志记录 :在黑名单拦截时记录日志,便于审计:

    lua 复制代码
    freeswitch.consoleLog("WARNING", "拦截被叫号码: " .. destination .. "\n")
  3. 误拦截恢复:提供管理接口(如 HTTP API)允许动态移除误加的黑名单条目。


6. 替代方案

如果 mod_blacklist 的默认功能无法满足需求,还可考虑:

  • mod_lua + 数据库:完全自定义逻辑。
  • mod_acl:基于 IP/CIDR 的访问控制(仅限 IP 层)。
  • mod_fail2ban:自动封禁恶意 IP(需配合系统日志)。

通过上述方法,可以灵活利用 mod_blacklist 结合 LuaJIT 实现被叫号码的黑名单管理,既能满足静态规则拦截,也能支持动态策略,适用于防欺诈、合规控制、资源保护等多种场景。


结合mysq,redis,http实现动态黑名单


分步解决方案

1. 数据库表优化(MySQL)

prefix 字段创建唯一索引,确保数据唯一性:

sql 复制代码
-- 检查现有表结构
DESC blacklist;

-- 添加唯一索引
ALTER TABLE blacklist ADD UNIQUE INDEX idx_prefix (prefix);
2. Redis 缓存配置(10号库)
  • 使用 SET 类型存储黑名单前缀,例如:

    bash 复制代码
    # 示例:添加前缀 110、120、119 到 Redis 10号库
    redis-cli -n 10 SADD blocked_prefixes "110" "120" "119"
3. LuaJIT 脚本实现

创建脚本 /usr/share/freeswitch/scripts/blacklist_check.lua,逻辑如下:

lua 复制代码
-- blacklist_check.lua
local log = freeswitch.consoleLog
local session = require "freeswitch.session"
local http = require "socket.http"
local ltn12 = require "ltn12"
local redis = require "redis"
local luasql = require "luasql.mysql"

-- 配置参数
local REDIS_HOST = "127.0.0.1"
local REDIS_PORT = 6379
local REDIS_DB = 10
local MYSQL_CONN_STR = "mysql://user:password@localhost/dbname"
local HTTP_API_URL = "http://blacklist-api/check?number="

-- 初始化 Redis 连接
local redis_client = redis.connect(REDIS_HOST, REDIS_PORT)
redis_client:select(REDIS_DB)

-- 初始化 MySQL 连接
local mysql_env = luasql.mysql()
local mysql_conn = mysql_env:connect("dbname", "user", "password", "localhost")

-- 检查紧急号码前缀(如 110、120、119)
local function is_emergency_number(destination)
    local emergency_prefixes = {"110", "120", "119"}
    for _, prefix in ipairs(emergency_prefixes) do
        if string.sub(destination, 1, #prefix) == prefix then
            return true
        end
    end
    return false
end

-- 查询 Redis 黑名单
local function check_redis(prefix)
    local exists = redis_client:sismember("blocked_prefixes", prefix)
    return exists == 1
end

-- 查询 MySQL 黑名单
local function check_mysql(prefix)
    local cursor = mysql_conn:execute(string.format("SELECT 1 FROM blacklist WHERE prefix = '%s' LIMIT 1", prefix))
    local row = cursor:fetch({}, "a")
    cursor:close()
    return row ~= nil
end

-- HTTP 查询并缓存结果
local function check_http_and_cache(prefix)
    local response_body = {}
    local res, code = http.request{
        url = HTTP_API_URL .. prefix,
        sink = ltn12.sink.table(response_body)
    }
    if code == 200 then
        local result = table.concat(response_body)
        if result == "BLOCK" then
            -- 写入 MySQL
            mysql_conn:execute(string.format("INSERT IGNORE INTO blacklist (prefix) VALUES ('%s')", prefix))
            -- 写入 Redis
            redis_client:sadd("blocked_prefixes", prefix)
            return true
        end
    end
    return false
end

-- 主逻辑
local function handle_call(session)
    local destination = session:getVariable("destination_number")
    if not destination then return session:continue() end

    -- 拦截紧急号码
    if is_emergency_number(destination) then
        session:hangup("CALL_REJECTED")
        return
    end

    -- 检查 Redis
    local blocked = check_redis(destination)
    if not blocked then
        -- 检查 MySQL
        blocked = check_mysql(destination)
        if not blocked then
            -- 查询 HTTP API
            blocked = check_http_and_cache(destination)
        else
            -- 缓存到 Redis
            redis_client:sadd("blocked_prefixes", destination)
        end
    end

    if blocked then
        session:answer()
        session:streamFile("ivr/blocked.wav")
        session:hangup("CALL_REJECTED")
    else
        session:continue()
    end
end

-- 执行
local sess = session.new()
handle_call(sess)
4. FreeSwitch 集成

在拨号计划中调用脚本:

xml 复制代码
<!-- /etc/freeswitch/dialplan/default/01_blacklist.xml -->
<extension name="blacklist_check">
  <condition field="destination_number" expression="^.*$">
    <action application="lua" data="blacklist_check.lua"/>
  </condition>
</extension>
5. 依赖安装(Ubuntu 22.04)

安装 LuaJIT、Redis 和 MySQL 客户端库:

bash 复制代码
sudo apt-get install -y luajit lua-socket lua-sec luarocks libmysqlclient-dev
sudo luarocks install luasql-mysql MYSQL_INCDIR=/usr/include/mysql
sudo luarocks install lua-resty-redis
6. 性能优化与验证
  • 连接池 :使用 lua-resty-redisluasql 的连接池功能。

  • 日志监控

    lua 复制代码
    log("NOTICE", string.format("拦截号码: %s\n", destination))
  • 压力测试 :使用 sipp 工具模拟高并发呼叫验证性能。


关键逻辑流程图

plaintext 复制代码
1. 获取被叫号码(destination_number)
   │
   ├─ 是紧急号码(110/120/119前缀)? → 拦截
   │
   └─ 否 → 查询 Redis
         │
         ├─ 存在 → 拦截
         │
         └─ 不存在 → 查询 MySQL
               │
               ├─ 存在 → 缓存到 Redis → 拦截
               │
               └─ 不存在 → 查询 HTTP API
                     │
                     ├─ 需拦截 → 写入 MySQL + Redis → 拦截
                     │
                     └─ 放行 → 继续路由

通过上述方案,可实现高效、动态的黑名单拦截,满足复杂业务场景需求。


针对紧急号码如110等的处理方案


分步解决方案:灵活控制紧急号码拦截


1. 数据库设计优化

在 MySQL 中创建两个新表,用于管理紧急号码拦截策略主叫白名单

sql 复制代码
-- 紧急号码拦截配置表
CREATE TABLE emergency_config (
  prefix VARCHAR(20) PRIMARY KEY COMMENT '紧急号码前缀(如110、120)',
  is_blocked TINYINT DEFAULT 1 COMMENT '是否拦截(1=拦截,0=放行)',
  description VARCHAR(255) COMMENT '规则描述'
);

-- 主叫白名单表(允许绕过紧急号码拦截)
CREATE TABLE emergency_whitelist (
  caller_id VARCHAR(20) PRIMARY KEY COMMENT '主叫号码',
  note VARCHAR(255) COMMENT '备注'
);

-- 插入示例数据
INSERT INTO emergency_config (prefix, is_blocked, description) VALUES
('110', 1, '默认拦截110'),
('120', 0, '放行120'),
('119', 1, '工作日拦截119');

INSERT INTO emergency_whitelist (caller_id, note) VALUES
('+8613912345678', '安保部门专用号码'),
('1001', '内部测试号码');

2. Redis 缓存策略更新

将紧急号码配置和白名单缓存到 Redis 10号库,使用 Hash 和 Set 结构:

bash 复制代码
# 紧急号码配置(Hash 存储)
redis-cli -n 10 HSET emergency:config 110 1 120 0 119 1

# 主叫白名单(Set 存储)
redis-cli -n 10 SADD emergency:whitelist +8613912345678 1001

3. LuaJIT 脚本增强逻辑

修改 /usr/share/freeswitch/scripts/blacklist_check.lua,添加灵活控制逻辑:

lua 复制代码
-- 新增函数:检查主叫是否在白名单
local function is_whitelisted_caller(caller_id)
    local exists = redis_client:sismember("emergency:whitelist", caller_id)
    return exists == 1
end

-- 新增函数:检查紧急号码是否需要拦截
local function check_emergency_block(destination, caller_id)
    -- 如果主叫在白名单,直接放行
    if is_whitelisted_caller(caller_id) then
        return false
    end

    -- 检查是否为紧急号码
    local emergency_prefixes = redis_client:hkeys("emergency:config")
    for _, prefix in ipairs(emergency_prefixes) do
        if string.sub(destination, 1, #prefix) == prefix then
            -- 从 Redis 获取拦截配置
            local block_flag = redis_client:hget("emergency:config", prefix)
            if block_flag == "1" then
                -- 附加条件:例如拦截119仅在工作日
                if prefix == "119" then
                    local weekday = os.date("%w") -- 0=周日,1=周一...
                    if weekday >= 1 and weekday <= 5 then
                        return true
                    else
                        return false
                    end
                end
                return true
            end
        end
    end
    return false
end

-- 修改主逻辑
local function handle_call(session)
    local destination = session:getVariable("destination_number")
    local caller_id = session:getVariable("caller_id_number")

    -- 检查紧急号码拦截(带条件)
    if check_emergency_block(destination, caller_id) then
        session:hangup("CALL_REJECTED")
        return
    end

    -- ...原有黑名单逻辑...
end

4. 动态配置更新机制

实现配置热更新(避免重启 FreeSwitch):

4.1 定时同步数据库到 Redis

创建 Python 脚本 /opt/scripts/sync_emergency_config.py

python 复制代码
import redis
import MySQLdb
import schedule
import time

def sync_config():
    # 连接 MySQL
    db = MySQLdb.connect("localhost","user","pass","db")
    cursor = db.cursor()

    # 同步紧急号码配置
    cursor.execute("SELECT prefix, is_blocked FROM emergency_config")
    r = redis.Redis(host='localhost', port=6379, db=10)
    r.delete("emergency:config")
    for row in cursor.fetchall():
        r.hset("emergency:config", row[0], row[1])

    # 同步主叫白名单
    cursor.execute("SELECT caller_id FROM emergency_whitelist")
    r.delete("emergency:whitelist")
    for row in cursor.fetchall():
        r.sadd("emergency:whitelist", row[0])

    db.close()

# 每5分钟同步一次
schedule.every(5).minutes.do(sync_config)

while True:
    schedule.run_pending()
    time.sleep(1)
4.2 使用 Systemd 管理同步服务

创建 /etc/systemd/system/emergency_sync.service

ini 复制代码
[Unit]
Description=Emergency Config Sync Service

[Service]
ExecStart=/usr/bin/python3 /opt/scripts/sync_emergency_config.py
Restart=always

[Install]
WantedBy=multi-user.target

启动服务:

bash 复制代码
sudo systemctl daemon-reload
sudo systemctl start emergency_sync
sudo systemctl enable emergency_sync

5. 多维度拦截控制示例
场景1:时间条件拦截

check_emergency_block 函数中添加时间判断:

lua 复制代码
-- 拦截110仅在 00:00-06:00
if prefix == "110" and block_flag == "1" then
    local hour = tonumber(os.date("%H"))
    if hour >= 0 and hour < 6 then
        return true
    else
        return false
    end
end
场景2:基于地理位置的放行

调用 IP 地理接口(需额外实现):

lua 复制代码
local function get_location(ip)
    local http = require "socket.http"
    local res, code = http.request("http://ip-api.com/json/"..ip)
    if code == 200 then
        return json.decode(res).countryCode
    end
    return nil
end

-- 在检查逻辑中添加:
local caller_ip = session:getVariable("sip_remote_ip")
if prefix == "119" and get_location(caller_ip) == "CN" then
    return false -- 国内IP放行119
end

6. 管理接口示例

通过 HTTP API 动态修改配置:

6.1 使用 Flask 创建管理 API

创建 /opt/scripts/emergency_api.py

python 复制代码
from flask import Flask, request
import MySQLdb
import redis

app = Flask(__name__)

@app.route('/update_emergency', methods=['POST'])
def update_config():
    data = request.json
    prefix = data['prefix']
    action = data['action']  # block/unblock
    
    # 更新数据库
    db = MySQLdb.connect("localhost","user","pass","db")
    cursor = db.cursor()
    cursor.execute(f"INSERT INTO emergency_config (prefix, is_blocked) VALUES ('{prefix}', {1 if action=='block' else 0}) ON DUPLICATE KEY UPDATE is_blocked=VALUES(is_blocked)")
    db.commit()
    
    # 立即同步到Redis
    r = redis.Redis(db=10)
    r.hset("emergency:config", prefix, 1 if action=='block' else 0)
    
    return {"status": "success"}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
6.2 调用示例
bash 复制代码
curl -X POST http://localhost:8080/update_emergency \
-H "Content-Type: application/json" \
-d '{"prefix": "110", "action": "unblock"}'

架构拓扑图

plaintext 复制代码
                +----------------+       +----------------+
                |   FreeSwitch   |       |   MySQL        |
                |                |       |                |
                |  LuaJIT Script |<----->| (黑名单配置)    |
                +--------+-------+       +--------+-------+
                         |                        |
                         |                        |
                +--------v-------+       +--------v-------+
                |   Redis (DB10) |       |  同步脚本       |
                |  (缓存配置)    |<------+ (Python)       |
                +--------+-------+       +----------------+
                         |
                         |
                +--------v-------+
                |  管理API       |
                |  (Flask)      |
                +----------------+

通过此方案,可实现:

  1. 紧急号码动态管控:通过数据库配置实时调整拦截策略
  2. 主叫白名单机制:允许特定号码绕过拦截
  3. 多维度条件判断:时间、地理位置、主叫身份等
  4. 高效缓存同步:确保配置变更秒级生效
  5. 运维友好性:提供 API 和脚本化运维接口

mod_translate【号码格式化】和mod_blacklist【黑明那单】两个模块的执行顺序建议


分步解决方案:mod_translatemod_blacklist 执行顺序详解


1. 核心原则

FreeSwitch 模块的执行顺序由 模块加载顺序事件订阅优先级 共同决定。mod_translate(号码转换)与 mod_blacklist(黑名单拦截)的协作需遵循以下原则:

  • 先拦截后转换 :若需基于原始号码进行拦截(如防止攻击者通过篡改号码绕过黑名单),应先执行 mod_blacklist
  • 先转换后拦截 :若需基于标准化后的号码进行拦截(如黑名单中存储的是转换后的完整号码),应先执行 mod_translate

2. 典型场景与配置

场景1:先拦截后转换(推荐安全模式)

需求

  • 直接拦截原始恶意号码(如 95013),无需转换。
  • 合法号码需转换为标准格式(如 95013 → +8610095013)。

配置方法

  1. 模块加载顺序

    确保 mod_blacklist 早于 mod_translate 加载(修改 modules.conf.xml):

    xml 复制代码
    <!-- 先加载黑名单模块 -->
    <load module="mod_blacklist"/>
    <!-- 再加载转换模块 -->
    <load module="mod_translate"/>
  2. 事件优先级调整 (可选):

    通过 mod_blacklistsession:check_blacklist 事件在 CHANNEL_CREATE 阶段拦截:

    xml 复制代码
    <!-- 在 vars.xml 中设置拦截事件优先级 -->
    <X-PRE-PROCESS cmd="set" data="blacklist_early_check=true"/>
  3. 拨号计划顺序

    dialplan 中明确逻辑顺序:

    xml 复制代码
    <extension name="blacklist_first">
      <!-- 1. 先执行黑名单检查 -->
      <condition>
        <action application="lua" data="blacklist_check.lua"/>
      </condition>
    </extension>
    
    <extension name="translate_after">
      <!-- 2. 号码转换 -->
      <condition field="destination_number" expression="^(\d+)$">
        <action application="translate" data="$1 /path/to/rules.xml"/>
      </condition>
    </extension>

场景2:先转换后拦截(业务适配模式)

需求

  • 拦截转换后的号码(如黑名单中存储国际格式 +8610095013)。
  • 需对原始号码(如 95013)先转换再检查。

配置方法

  1. 模块加载顺序

    确保 mod_translate 早于 mod_blacklist 加载:

    xml 复制代码
    <load module="mod_translate"/>
    <load module="mod_blacklist"/>
  2. 转换规则配置rules.xml):

    xml 复制代码
    <rule name="Local to E164">
      <match>^(\d{5})$</match> <!-- 匹配短号 95013 -->
      <action>+86100$1</action> <!-- 转换为 +8610095013 -->
    </rule>
  3. 黑名单配置

    mod_blacklist 中设置拦截转换后的号码:

    xml 复制代码
    <param name="blacklist" value="^\+8610095013$"/>
  4. Lua 脚本适配

    blacklist_check.lua 中检查转换后的号码:

    lua 复制代码
    local destination = session:getVariable("destination_number")
    -- 直接使用转换后的号码
    if blacklist.check(destination) then
      session:hangup()
    end

3. 混合模式(动态决策)

需求

  • 对某些号码(如 VIP)直接放行,不触发转换。
  • 对普通号码先转换再拦截。

实现方法

xml 复制代码
<!-- 拨号计划分层次处理 -->
<extension name="vip_bypass">
  <condition field="${vip_caller}" expression="^true$">
    <!-- VIP 主叫直接跳过转换和拦截 -->
    <action application="bridge" data="user/${destination_number}"/>
  </condition>
</extension>

<extension name="normal_process">
  <!-- 1. 普通号码先转换 -->
  <condition>
    <action application="translate" data="${destination_number} /path/to/rules.xml"/>
  </condition>
  <!-- 2. 再拦截 -->
  <condition>
    <action application="lua" data="blacklist_check.lua"/>
  </condition>
</extension>

4. 性能与安全注意事项

  1. 模块加载顺序验证

    通过 fs_cli 命令 module_exists mod_blacklist 检查模块是否按需加载。

  2. 日志调试

    在关键节点添加日志,观察执行顺序:

    lua 复制代码
    -- Lua 脚本中
    freeswitch.consoleLog("NOTICE", "转换前号码: " .. orig_number .. "\n")
    freeswitch.consoleLog("NOTICE", "转换后号码: " .. new_number .. "\n")
  3. 正则表达式优化

    • mod_blacklist 使用高效正则(如 ^\+?\d+$ 避免回溯)。
    • mod_translate 的规则文件采用 最长匹配优先 策略。
  4. 防御性设计

    xml 复制代码
    <!-- 防止转换模块被绕过 -->
    <param name="translate_suppress_leg" value="true"/>

5. 执行顺序流程图

plaintext 复制代码
                     +-----------------+
                     |  SIP INVITE     |
                     +--------+--------+
                              |
               +--------------+--------------+
               | 模块拦截顺序                |
               | 1. mod_blacklist (if loaded first)
               | 2. mod_translate (if loaded next)
               +--------------+--------------+
                              |
               +--------------+--------------+
               | 拨号计划逻辑顺序            |
               | 1. 黑名单检查(Lua/XML)    |
               | 2. 号码转换(translate)    |
               +--------------+--------------+
                              |
                     +--------v--------+
                     | 路由决策       |
                     +-----------------+

总结

  • 安全优先场景mod_blacklistmod_translate(原始号码拦截)。
  • 业务适配场景mod_translatemod_blacklist(标准化后拦截)。
  • 混合模式 :通过拨号计划分层次控制,兼顾灵活性与安全性。
    实际配置需结合具体业务需求,通过模块加载顺序、拨号计划优先级、日志监控三者协同验证。
相关推荐
狂爱代码的码农2 天前
FreeSwitch之mod_cidlookup 和 mod_blacklist和mod_curl的抉择
freeswitch
狂爱代码的码农19 天前
freeswitch在centos上编译过程
freeswitch
狂爱代码的码农1 个月前
debian12.9编译freeswitch1.10.12【默认安装】
freeswitch
Mike_Zhang2 个月前
FreeSWITCH日志功能分析及apr模拟
voip·freeswitch
贾宝玉的玉宝贾2 个月前
FreeSWITCH 简单图形化界面39 - Windows安装FreeSWITCH For IPPBX(WSL环境)
windows·voip·freeswitch·ippbx·sip测试
贾宝玉的玉宝贾2 个月前
FreeSWITCH 简单图形化界面38 - 使用uniapp中使用JsSIP进行音视频呼叫
uni-app·音视频·voip·freeswitch·ippbx·jssip
代码浪人2 个月前
docker 基于Debian镜像安装FreeSwitch1.10.7
docker·容器·debian·freeswitch
new_abc3 个月前
Sofia-SIP 使用教程
freeswitch·sofia
hongkid3 个月前
docker 部署freeswitch(非编译方式)
docker·容器·freeswitch