jvm-sandbox-repeater时间mock插件设计与实现

本文介绍了如何在JVMsandboxrepeater中实现时间mock功能,重点讲解了如何使用插件mock`System.currentTimeMillis()`,设计了一个日期插件并处理实践中遇到的问题,如原生代码支持和SpringMVC控制器中Date参数的处理。

一、背景

jvm-sandbox-repeater实现了基础的录制回放流程编排,并简单的给了几个插件的demo,离实际项目运用其实还需要二次开发很多东西,其中时间mock能力是一个非常基础的能力,业务代码里经常需要用到这块;

二、调研

2.1 如何mock当前时间

我们mock的主要是"当前时间",java里获取当前时间的主要方式是以下两种(LocalDate其实也很常用,但是我没有去做mock了,感兴趣的参考文档自行开发):

  1. java.util.Date new Date() 获取当前时间
  2. System.currentTimeMillis() 获取当前时间

new Date()构造函数实现中,我们发现取当前时间调用的就是System.currentTimeMillis(),因此我们只需要mock System.currentTimeMillis()即可

//默认构造函数
public Date() {
    //这里取的就是System.currentTimeMillis(), 所以我们只需要mock 
    this(System.currentTimeMillis());
}

2.2 基于sandbox怎么实现

我自己经过多次测试,下面的实现方式是能够有效拦截并生效的,因此sandbox是有能力拦截java native实现的, 基于此,怎么实现就简单了;

@MetaInfServices(Module.class)
@Information(id = "date-mocker")
public class DateMockModule  implements Module {

    @Resource
    private ModuleEventWatcher moduleEventWatcher;

    @Command("repairCheckState")
    public void repairCheckState() {

        new EventWatchBuilder(moduleEventWatcher)
                .onClass(System.class)
                .includeBootstrap()
                .onBehavior("currentTimeMillis")
                .onWatch(
                        new AdviceListener() {
                            protected void before(Advice advice) throws Throwable {
                                System.out.println("come here");
                            }
                        }
                );
    }
}

三、设计与实现

3.1 初步设计

基本的流程如下:

  1. 拦截System.currentTimeMillis();
  2. 判断本次调用是否为回放流量,如果不是回放流量,调用System.currentTimeMillis()原生逻辑返回结果
  3. 如果是回放流程,则从采集上下文中取特定时间作为返回结果即可

看似简单的流程,实现过程中会遇到如下问题:

  1. 拦截 System.currentTimeMillis() 怎么实现
  2. 如何判断本次流量是否为回放流量
  3. 取什么时间作为mock的时间

接下来,我们针对上面的问题具体解答;

3.2 jvm-sandbox-repeater新增一个Date插件

新增插件需要定义以下三个东西

  • 继承 AbstractInvokePluginAdapter,定义插件类型、名称以及拦截点
  • 定义EventListener, 处理拦截点返回的BEFORE/RETURN/THROWS等事件;
  • 定义InvocationProcessor, 根据拦截信息组装Invocation信息,或者mock的时候直接返回结果;

首先定义DatePlugin

package com.alibaba.jvm.sandbox.repeater.plugin.date;

import com.alibaba.jvm.sandbox.api.event.Event;
import com.alibaba.jvm.sandbox.api.listener.EventListener;
import com.alibaba.jvm.sandbox.repeater.plugin.api.InvocationListener;
import com.alibaba.jvm.sandbox.repeater.plugin.api.InvocationProcessor;
import com.alibaba.jvm.sandbox.repeater.plugin.core.impl.AbstractInvokePluginAdapter;
import com.alibaba.jvm.sandbox.repeater.plugin.core.model.EnhanceModel;
import com.alibaba.jvm.sandbox.repeater.plugin.domain.InvokeType;
import com.alibaba.jvm.sandbox.repeater.plugin.spi.InvokePlugin;
import com.google.common.collect.Lists;
import org.kohsuke.MetaInfServices;

import java.util.List;

@MetaInfServices(InvokePlugin.class)
public class DatePlugin extends AbstractInvokePluginAdapter {

    @Override
    public InvokeType getType() {
        return InvokeType.JAVA_DATE;
    }

    @Override
    public String identity() {
        return "java-date";
    }

    @Override
    public boolean isEntrance() {
        return false;
    }

    @Override
    protected List<EnhanceModel> getEnhanceModels() {
        
        //这里是拦截点信息
        EnhanceModel em = EnhanceModel.builder()
                //这里需要扩展支持下,sandbox操作原生类需要支持
                .includeBootstrap(true)
                .classPattern("java.lang.System")
                .methodPatterns(EnhanceModel.MethodPattern.transform("currentTimeMillis"))
                .watchTypes(Event.Type.BEFORE, Event.Type.RETURN, Event.Type.THROWS, Event.Type.CALL_RETURN)
                .build();

        return Lists.newArrayList(em);
    }

    protected EventListener getEventListener(InvocationListener listener) {
        return new DatePluginEventListener(getType(), isEntrance(), listener, getInvocationProcessor());
    }

    @Override
    protected InvocationProcessor getInvocationProcessor() {
        return new DatePluginProcessor(getType());
    }

}

再定义DatePluginEventListener

package com.alibaba.jvm.sandbox.repeater.plugin.date;

import com.alibaba.jvm.sandbox.api.ProcessControlException;
import com.alibaba.jvm.sandbox.api.event.BeforeEvent;
import com.alibaba.jvm.sandbox.api.event.Event;
import com.alibaba.jvm.sandbox.repeater.plugin.api.InvocationListener;
import com.alibaba.jvm.sandbox.repeater.plugin.api.InvocationProcessor;
import com.alibaba.jvm.sandbox.repeater.plugin.core.cache.RepeatCache;
import com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultEventListener;
import com.alibaba.jvm.sandbox.repeater.plugin.core.trace.Tracer;
import com.alibaba.jvm.sandbox.repeater.plugin.domain.InvokeType;
import com.alibaba.jvm.sandbox.repeater.plugin.domain.RepeatContext;

import java.util.Date;

public class DatePluginEventListener extends DefaultEventListener {

    public DatePluginEventListener(InvokeType invokeType, boolean entrance, InvocationListener listener, InvocationProcessor processor) {
        super(invokeType, entrance, listener, processor);
    }

    @Override
    public void onEvent(Event event) throws Throwable {
        if (!event.type.equals(Event.Type.BEFORE)) {
            return;
        }

        BeforeEvent e = (BeforeEvent) event;


        //只处理回放流量
        if (RepeatCache.isRepeatFlow(Tracer.getTraceId())) {

            //processor.doMock(event, entrance, invokeType);
            RepeatContext repeatContext = RepeatCache.getRepeatContext(Tracer.getTraceId());
            if (repeatContext == null) {
                return;
            }
            
            //特殊场景必须这么判断
            if (!repeatContext.getCanMockDate()) {
                return;
            }

            //获取录制时间
            long recordTime = repeatContext.getRecordModel().getTimestamp();

            if (e.javaClassName.equals("java.lang.System")) {
                //这里是sandbox的一个约定,抛异常直接返回结果
                ProcessControlException.throwReturnImmediately(recordTime);
            }
        }
    }
}

最后定义DatePluginProcessor:

package com.alibaba.jvm.sandbox.repeater.plugin.date;


import com.alibaba.jvm.sandbox.api.event.InvokeEvent;
import com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultInvocationProcessor;
import com.alibaba.jvm.sandbox.repeater.plugin.domain.InvokeType;

import static com.alibaba.jvm.sandbox.api.event.Event.Type.BEFORE;

public class DatePluginProcessor extends DefaultInvocationProcessor {

    public DatePluginProcessor(InvokeType type) {
        super(type);
    }


    @Override
    public boolean ignoreEvent(InvokeEvent event) {
        if (!event.type.equals(BEFORE)) {
            return true;
        }

        return false;
    }
}

整个插件的结构如下


代码实现可以到我的github下:
https://github.com/penghu2/sandbox-repeater/tree/master/repeater-plugins/date-plugin

四、实践过程中遇到的问题

4.1 jvm-sandbox-repeater 原生代码不支持 includeBootstrap

com.alibaba.jvm.sandbox.repeater.plugin.core.model.EnhanceModel.EnhanceModelBuilder 里没有地方可以设置 includeBootstrap, 这个需要自己支持下,因为这个比较简单,我就不再多说;

4.2 spring mvc controller @RequestBody中带Date的,以及java主调用请求参数中的Date是不可以mock的

我们判断流量是否为回放流量,有2段逻辑:

if (RepeatCache.isRepeatFlow(Tracer.getTraceId())) {

            //processor.doMock(event, entrance, invokeType);
            RepeatContext repeatContext = RepeatCache.getRepeatContext(Tracer.getTraceId());
            if (repeatContext == null) {
                return;
            }
            
            //特殊场景必须这么判断
            if (!repeatContext.getCanMockDate()) {
                return;
            }
}

RepeatCache.isRepeatFlow(Tracer.getTraceId()) 是从线程变量里判断本次是否为回放流量;!repeatContext.getCanMockDate()是为了确保 回放的入参都初始化之后在mock时间,否则会导致入参被覆盖!repeatContext中我们定义了个变量boolean canMockDate,这个变量的修改放在了HttpPlugin里:

package com.alibaba.jvm.sandbox.repater.plugin.http;

/**
 * {@link HttpPlugin} http入口流量类型插件
 * <p>
 *
 * @author zhaoyb1990
 */
@MetaInfServices(InvokePlugin.class)
public class HttpPlugin extends AbstractInvokePluginAdapter {

    。。。。省略其他冗余代码

    @Override
    public void onLoaded() throws PluginLifeCycleException {
        new EventWatchBuilder(watcher)
                .onClass("org.springframework.web.method.support.InvocableHandlerMethod")
                .onBehavior("doInvoke")
                .onWatch(new AdviceListener() {

                    protected void before(Advice advice) throws Throwable {
                        if (RepeatCache.isRepeatFlow(Tracer.getTraceId())) {
                            RepeatContext repeatContext = RepeatCache.getRepeatContext(Tracer.getTraceId());
                            if (repeatContext!=null) {
                                repeatContext.setCanMockDate(true);
                            }

                        }
                    }
                    protected void afterReturning(Advice advice) throws Throwable {
                        if (RepeatCache.isRepeatFlow(Tracer.getTraceId())) {
                            RepeatContext repeatContext = RepeatCache.getRepeatContext(Tracer.getTraceId());
                            if (repeatContext!=null) {
                                repeatContext.setCanMockDate(false);
                            }
                        }
                    }

                });
    }
}

我们拦截了org.springframework.web.method.support.InvocableHandlerMethod#doInvoke的入口,在执行之前repeatContext.setCanMockDate(true),执行之后 repeatContext.setCanMockDate(false);那为什么是这里拦截呢,就需要你自行去调研了(调研下spring @RequestBody参数初始化流程即可)~~

本文由mdnice多平台发布

<think>好的,我需要帮助用户决定是否使用Jvm-sandbox-repeater工具还是自己实现类似的功能。用户之前询问过关于Goreplay和Jvm-sandbox-repeater的信息,现在想进一步了解选择现成工具还是自研的优缺点。 首先,我应该回顾Jvm-sandbox-repeater的核心功能。它主要用于Java应用的流量录制和回放,通过字节码增强技术非侵入式地监控方法调用,适合漏洞检测、回归测试等场景。用户可能在做毕设,涉及Java应用的安全检测或测试,需要评估工具是否满足需求。 接下来,用户的问题关键在于比较使用现有工具和自研的利弊。我需要列出两者的优缺点,考虑时间成本、技术难度、功能需求、维护成本等因素。例如,Jvm-sandbox-repeater已经提供了成熟的录制和回放机制,节省开发时间,但可能不够灵活;而自研可以定制功能,但开发周期长,技术要求高。 然后,我需要考虑用户的毕设要求和资源。如果毕设时间紧迫,或者用户对Java字节码增强技术不熟悉,推荐使用现成工具更合适。如果毕设的核心创新点在于实现一个独特的录制回放机制,或者现有工具无法满足特定需求,自研可能更好。 还要分析用户可能的需求场景。比如,如果用户需要深度监控Spring Boot应用内部的方法调用和上下文,Jvm-sandbox-repeater已经支持这些,自研可能需要重复造轮子。但如果用户的需求非常特殊,比如需要集成特定的安全检测算法,现有工具扩展性不足,可能需要自研。 另外,需要提醒用户注意技术风险。比如,使用Jvm-sandbox-repeater可能存在性能开销,而自研可能遇到技术难点如字节码操作、稳定性问题等。同时,维护成本也是一个因素,现成工具有社区支持,自研则需要自己维护。 最后,结合用户之前的提问,他们的毕设可能涉及流量录制、漏洞检测,可能结合了Goreplay和Jvm-sandbox-repeater。需要建议在现有工具基础上进行扩展,或者根据需求选择最合适的方案,并给出具体场景的例子,帮助用户做出决策。</think>以下是关于**使用Jvm-sandbox-repeater工具****自研实现**的深度对比分析,结合技术实现成本、功能完备性、毕设适配性等维度给出建议: --- ### 一、核心能力对比 | **维度** | **Jvm-sandbox-repeater** | **自研实现** | |-------------------------|---------------------------------------------|---------------------------------------| | **功能范围** | 完整支持方法级录制/回放、上下文关联、MOCK参数注入 | 需自行实现核心功能模块 | | **技术门槛** | 依赖Java Agent+ASM字节码增强技术(已有封装) | 需深入掌握JVM TI、类加载机制、字节码操控 | | **开发周期** | 1-3天集成配置 | 至少1-2个月(含调试) | | **稳定性** | 经过阿里生产环境验证 | 需自行解决兼容性问题(如Spring AOP冲突)| | **扩展性** | 支持插件开发,但受限于框架设计 | 完全自主可控,可定制核心逻辑 | --- ### 二、关键决策因素 #### 1. **选择Jvm-sandbox-repeater的条件** - **场景符合性高** 若你的毕设需求以下场景高度匹配,优先使用现成工具: - 需要录制`Spring Boot/Dubbo`接口的**方法调用链**(如监控`$$ UserService.findById(Integer\ id) $$`的入参和返回值) - 要求非侵入式集成(无需修改业务代码) - 需要快速验证漏洞检测算法(直接利用其回放能力) - **代码示例:快速集成** 在Spring Boot启动脚本中添加Agent参数即可: ```bash java -javaagent:/path/to/sandbox-core.jar \ -Drepeater.config.path=/config/vuln-detection.json \ -jar your-application.jar ``` #### 2. **需要自研的典型场景** - **特殊需求无法满足** - 需监控非JVM语言组件(如Python服务调用) - 要求毫秒级延迟的录制(Jvm-sandbox默认有`$5\sim15ms$`的方法拦截开销) - 需要定制化数据存储格式(如直接对接毕设的**图数据库**存储调用链路) - **学术创新需求** 若毕设核心价值在于**新型录制技术**(例如: $$ \text{基于因果推理的智能流量采样算法} $$ 则必须自研以体现创新性) --- ### 三、风险-收益分析 #### 1. **使用现成工具的风险** | 风险类型 | 具体表现 | 应对方案 | |----------------|-----------------------------------------|---------------------------------------| | 性能损耗 | 高频方法调用可能导致`QPS下降10%~20%` | 仅录制关键接口,通过`@Module`注解排除非核心类 | | 上下文丢失 | 无法自动捕获ThreadLocal以外的自定义上下文 | 通过`repeater-config.xml`手动补充采集逻辑 | | 版本兼容性 | 对Java 17+或GraalVM支持有限 | 降级到Java 11运行环境 | #### 2. **自研实现的挑战** ```mermaid graph TD A[启动自研项目] --> B{技术难点} B --> C[字节码增强稳定性] B --> D[上下文序列化] B --> E[回放一致性保障] C --> F((需反复测试ClassLoader机制)) D --> G((需处理不可序列化对象)) E --> H((需实现参数模糊化)) ``` --- ### 四、决策树模型 通过以下问题快速判断方向: 1. 毕设核心是否聚焦**录制回放技术本身**? - 是 → 自研 - 否 → 使用工具 2. 是否需要深度关联**Java方法调用链漏洞检测**? - 是 → Jvm-sandbox-repeater - 否 → 评估其他工具 3. 是否有足够时间攻克**JVM底层技术**? - 是 → 自研(可提升论文技术深度) - 否 → 使用工具 --- ### 五、折中方案建议 #### **"工具改造"模式** 在Jvm-sandbox-repeater基础上扩展功能: 1. 添加自定义插件: ```java @MetaInfServices(Plugin.class) public class SQLInjectionPlugin implements Plugin { @Override public void watch(RepeaterConfig config) { config.registerEventListener(SqlExecuteEvent.class, (event) -> { String sql = event.getSql(); if (checkSQLi(sql)) { // 你的检测算法 reportVulnerability(event); } }); } } ``` 2. 修改数据上报模块,对接毕设的**威胁情报平台** #### **"混合架构"模式** - 使用Jvm-sandbox做基础数据采集 - 自研**智能分析层**(如用机器学习模型分析录制的调用链) --- ### 六、结论 - **推荐选择Jvm-sandbox-repeater**,如果: - 毕设重点在漏洞检测算法而非底层采集 - 时间有限且需快速产出Demo - **建议自研**,如果: - 现有工具需求存在根本性冲突 - 课题组有JVM专家提供技术支持 - 学术创新需要完全自主的技术方案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值