当前位置: 首页 > news >正文

SOME/IP:用Python实现协议订阅、Offer、订阅ACK与报文接收

文章目录

  • 前言
  • 一、代码层次
  • 二、详细代码
    • 1. eth_scapy_sd.py
    • 2、eth_scapy_someip.py
    • 3、network_define.py
    • 4、packet_define.py
    • 5、unpack_define.py
    • 6、someip_controller.py


前言

1、需要pip安装scapy库
2、需要修改根据实际情况配置network_define.py
3、执行someip_controller.py运行本案例
4、本文参考github: eth-scapy-someip

注:最近没啥时间写文章,有兴趣自己研究下<~~>


一、代码层次

someip/
└── module/├── eth_scapy_sd.py          # SOME/IP SD服务发现结构体定义├── eth_scapy_someip.py      # SOME/IP标准结构体定义├── network_define.py        # 网络配置(IP/端口/多播组)├── packet_define.py         # 协议字段定义(Offer/Subscribe/ACK)├── unpack_define.py         # 接收报文解析器└── someip_controller.py     # 主控制逻辑(订阅/OFFER/ACK流程管理)

二、详细代码

1. eth_scapy_sd.py

import ctypes
import collectionsfrom eth_scapy_someip import SOMEIPfrom scapy.fields import *
from scapy.packet import *
from scapy.layers.inet6 import IP6Fieldclass _SDPacketBase(Packet):"""base class to be used among all SD Packet definitions."""# use this dictionary to set default values for desired fields (mostly on subclasses# where not all fields are defined locally)# - key : field_name, value : desired value# - it will be used from 'init_fields' function, upon packet initialization## example : _defaults = {'field_1_name':field_1_value,'field_2_name':field_2_value}_defaults = {}def _set_defaults(self):"""goes through '_defaults' dict setting field default values (for those that have been defined)."""for key in self._defaults.keys():try:self.get_field(key)except KeyError:passelse:self.setfieldval(key, self._defaults[key])def init_fields(self):"""perform initialization of packet fields with desired values.NOTE : this funtion will only be called *once* upon class (or subclass) construction"""Packet.init_fields(self)self._set_defaults()# SD ENTRY
#  - Service
#  - EventGroup
class _SDEntry(_SDPacketBase):"""Base class for SDEntry_* packages."""TYPE_FMT = ">B"TYPE_PAYLOAD_I = 0# ENTRY TYPES : SERVICETYPE_SRV_FINDSERVICE = 0x00TYPE_SRV_OFFERSERVICE = 0x01TYPE_SRV = (TYPE_SRV_FINDSERVICE, TYPE_SRV_OFFERSERVICE)# ENTRY TYPES : EVENGROUPTYPE_EVTGRP_SUBSCRIBE = 0x06TYPE_EVTGRP_SUBSCRIBE_ACK = 0x07TYPE_EVTGRP = (TYPE_EVTGRP_SUBSCRIBE, TYPE_EVTGRP_SUBSCRIBE_ACK)# overall len (UT usage)OVERALL_LEN = 16# 定义Entries Array结构体fields_desc = [ByteField("type", 0),ByteField("index_1", 0),ByteField("index_2", 0),BitField("n_opt_1", 0, 4),BitField("n_opt_2", 0, 4),ShortField("srv_id", 0),ShortField("inst_id", 0),ByteField("major_ver", 0),X3BytesField("ttl", 0),]def guess_payload_class(self, payload):"""decode SDEntry depending on its type."""pl_type = struct.unpack(_SDEntry.TYPE_FMT,payload[_SDEntry.TYPE_PAYLOAD_I : _SDEntry.TYPE_PAYLOAD_I + 1],)[0]if pl_type in _SDEntry.TYPE_SRV:return SDEntry_Serviceelif pl_type in _SDEntry.TYPE_EVTGRP:return SDEntry_EventGroupclass SDEntry_Service(_SDEntry):"""Service Entry."""_defaults = {"type": _SDEntry.TYPE_SRV_FINDSERVICE}name = "Service Entry"fields_desc = [_SDEntry, IntField("minor_ver", 0)]class SDEntry_EventGroup(_SDEntry):"""EventGroup Entry."""_defaults = {"type": _SDEntry.TYPE_EVTGRP_SUBSCRIBE}name = "Eventgroup Entry"fields_desc = [_SDEntry,BitField("res", 0, 12),BitField("cnt", 0, 4),ShortField("eventgroup_id", 0),]# SD Option
#  - Configuration
#  - LoadBalancing
#  - IPv4 EndPoint
#  - IPv6 EndPoint
#  - IPv4 MultiCast
#  - IPv6 MultiCast
#  - IPv4 EndPoint
#  - IPv6 EndPoint
class _SDOption(_SDPacketBase):"""Base class for SDOption_* packages."""TYPE_FMT = ">B"TYPE_PAYLOAD_I = 2CFG_TYPE = 0x01CFG_OVERALL_LEN = (4  # overall length of CFG SDOption,empty 'cfg_str' (to be used from UT))LOADBALANCE_TYPE = 0x02LOADBALANCE_LEN = 0x05LOADBALANCE_OVERALL_LEN = 8  # overall length of LB SDOption (to be used from UT)IP4_ENDPOINT_TYPE = 0x04IP4_ENDPOINT_LEN = 0x0009IP4_MCAST_TYPE = 0x14IP4_MCAST_LEN = 0x0009IP4_SDENDPOINT_TYPE = 0x24IP4_SDENDPOINT_LEN = 0x0009IP4_OVERALL_LEN = 12  # overall length of IP4 SDOption (to be used from UT)IP6_ENDPOINT_TYPE = 0x06IP6_ENDPOINT_LEN = 0x0015IP6_MCAST_TYPE = 0x16IP6_MCAST_LEN = 0x0015IP6_SDENDPOINT_TYPE = 0x26IP6_SDENDPOINT_LEN = 0x0015IP6_OVERALL_LEN = 24  # overall length of IP6 SDOption (to be used from UT)def guess_payload_class(self, payload):"""decode SDOption depending on its type."""pl_type = struct.unpack(_SDOption.TYPE_FMT,payload[_SDOption.TYPE_PAYLOAD_I : _SDOption.TYPE_PAYLOAD_I + 1],)[0]if pl_type == _SDOption.CFG_TYPE:return SDOption_Configelif pl_type == self.LOADBALANCE_TYPE:return SDOption_LoadBalanceelif pl_type == self.IP4_ENDPOINT_TYPE:return SDOption_IP4_EndPointelif pl_type == self.IP4_MCAST_TYPE:return SDOption_IP4_Multicastelif pl_type == self.IP4_SDENDPOINT_TYPE:return SDOption_IP4_SD_EndPointelif pl_type == self.IP6_ENDPOINT_TYPE:return SDOption_IP6_EndPointelif pl_type == self.IP6_MCAST_TYPE:return SDOption_IP6_Multicastelif pl_type == self.IP6_SDENDPOINT_TYPE:return SDOption_IP6_SD_EndPointclass _SDOption_Header(_SDOption):fields_desc = [ShortField("len", None),ByteField("type", 0),ByteField("res_hdr", 0),]class _SDOption_Tail(_SDOption):fields_desc = [ByteField("res_tail", 0),ByteEnumField("l4_proto", 0x06, {0x06: "TCP", 0x11: "UDP"}),ShortField("port", 0),]class _SDOption_IP4(_SDOption):fields_desc = [_SDOption_Header, IPField("addr", "0.0.0.0"), _SDOption_Tail]class _SDOption_IP6(_SDOption):fields_desc = [_SDOption_Header,IP6Field("addr", "2001:cdba:0000:0000:0000:0000:3257:9652"),_SDOption_Tail,]class SDOption_Config(_SDOption):# offset to be added upon length calculation (corresponding to header's "Reserved" field)LEN_OFFSET = 0x01name = "Config Option"# default values specification_defaults = {"type": _SDOption.CFG_TYPE}# package fields definitonfields_desc = [_SDOption_Header, StrField("cfg_str", "")]def post_build(self, p, pay):# length computation excluding 16b_length and 8b_typel = self.lenif l is None:l = len(self.cfg_str) + self.LEN_OFFSETp = struct.pack("!H", l) + p[2:]return p + payclass SDOption_LoadBalance(_SDOption):name = "LoadBalance Option"# default values specification_defaults = {"type": _SDOption.LOADBALANCE_TYPE, "len": _SDOption.LOADBALANCE_LEN}# package fields definitonfields_desc = [_SDOption_Header, ShortField("priority", 0), ShortField("weight", 0)]# SDOPTIONS : IPv4-specific
class SDOption_IP4_EndPoint(_SDOption_IP4):name = "IP4 EndPoint Option"# default values specification_defaults = {"type": _SDOption.IP4_ENDPOINT_TYPE, "len": _SDOption.IP4_ENDPOINT_LEN}class SDOption_IP4_Multicast(_SDOption_IP4):name = "IP4 Multicast Option"# default values specification_defaults = {"type": _SDOption.IP4_MCAST_TYPE, "len": _SDOption.IP4_MCAST_LEN}class SDOption_IP4_SD_EndPoint(_SDOption_IP4):name = "IP4 SDEndPoint Option"# default values specification_defaults = {"type": _SDOption.IP4_SDENDPOINT_TYPE,"len": _SDOption.IP4_SDENDPOINT_LEN,}# SDOPTIONS : IPv6-specific
class SDOption_IP6_EndPoint(_SDOption_IP6):name = "IP6 EndPoint Option"# default values specification_defaults = {"type": _SDOption.IP6_ENDPOINT_TYPE, "len": _SDOption.IP6_ENDPOINT_LEN}class SDOption_IP6_Multicast(_SDOption_IP6):name = "IP6 Multicast Option"# default values specification_defaults = {"type": _SDOption.IP6_MCAST_TYPE, "len": _SDOption.IP6_MCAST_LEN}class SDOption_IP6_SD_EndPoint(_SDOption_IP6):name = "IP6 SDEndPoint Option"# default values specification_defaults = {"type": _SDOption.IP6_SDENDPOINT_TYPE,"len": _SDOption.IP6_SDENDPOINT_LEN,}#
# SD PACKAGE DEFINITION
#
class SD(_SDPacketBase):"""SD PacketNOTE :   when adding 'entries' or 'options', do not use list.append() method but create a new liste.g. :  p = SD()p.option_array = [SDOption_Config(),SDOption_IP6_EndPoint()]"""SOMEIP_MSGID_SRVID = 0xFFFFSOMEIP_MSGID_SUBID = 0x1SOMEIP_MSGID_EVENTID = 0x100SOMEIP_PROTO_VER = 0x01SOMEIP_IFACE_VER = 0x01SOMEIP_MSG_TYPE = SOMEIP.TYPE_NOTIFICATIONname = "SD"# Flags definition: {"name":(mask,offset)}_sdFlag = collections.namedtuple("Flag", "mask offset")FLAGSDEF = {"REBOOT": _sdFlag(mask=0x80, offset=7),  # ReBoot flag"UNICAST": _sdFlag(mask=0x40, offset=6),  # UniCast flag}fields_desc = [ByteField("flags", 0),X3BytesField("res", 0),FieldLenField(name="len_entry_array", default=None, length_of="entry_array", fmt="!I"),PacketListField(name="entry_array",default=None,pkt_cls=_SDEntry,length_from=lambda pkt: pkt.len_entry_array,),FieldLenField(name="len_option_array", default=None, length_of="option_array", fmt="!I"),PacketListField(name="option_array",default=None,pkt_cls=_SDOption,length_from=lambda pkt: pkt.len_option_array,),]def __init__(self, *args, **kwargs):super(SD, self).__init__(*args, **kwargs)self.explicit = 1def getFlag(self, name):"""get particular flag from bitfield."""name = name.upper()if name in self.FLAGSDEF:return (self.flags & self.FLAGSDEF[name].mask) >> self.FLAGSDEF[name].offsetelse:return Nonedef setFlag(self, name, value):"""Set particular flag on bitfield.:param str name : name of the flag to set (see SD.FLAGSDEF):param int value : either 0x1 or 0x0 (provided int will be ANDed with 0x01)"""name = name.upper()if name in self.FLAGSDEF:self.flags = (self.flags & ctypes.c_ubyte(~self.FLAGSDEF[name].mask).value) | (value & 0x01) << self.FLAGSDEF[name].offsetdef setEntryArray(self, entry_list):"""Add entries to entry_array.:param entry_list: list of entries to be added. Single entry object also accepted"""if isinstance(entry_list, list):self.entry_array = entry_listelse:self.entry_array = [entry_list]def setOptionArray(self, option_list):"""Add options to option_array.:param option_list: list of options to be added. Single option object also accepted"""if isinstance(option_list, list):self.option_array = option_listelse:self.option_array = [option_list]def getSomeip(self, stacked=False):"""return SD-initialized SOME/IP packet:param stacked: boolean. Either just SOME/IP packet or stacked over SD-self"""p = SOMEIP()p.msg_id.srv_id = SD.SOMEIP_MSGID_SRVIDp.msg_id.sub_id = SD.SOMEIP_MSGID_SUBIDp.msg_id.event_id = SD.SOMEIP_MSGID_EVENTIDp.proto_ver = SD.SOMEIP_PROTO_VERp.iface_ver = SD.SOMEIP_IFACE_VERp.msg_type = SD.SOMEIP_MSG_TYPEif stacked:return p / selfelse:return pdef get_someip_with_session_id(self,session_id, stacked=False):p = SOMEIP()p.msg_id.srv_id = SD.SOMEIP_MSGID_SRVIDp.msg_id.sub_id = SD.SOMEIP_MSGID_SUBIDp.msg_id.event_id = SD.SOMEIP_MSGID_EVENTIDp.proto_ver = SD.SOMEIP_PROTO_VERp.iface_ver = SD.SOMEIP_IFACE_VERp.msg_type = SD.SOMEIP_MSG_TYPEp.req_id.session_id = session_idif stacked:return p / selfelse:return p

2、eth_scapy_someip.py

from scapy.layers.inet import *
from scapy.fields import *
from scapy.packet import *"""SOMEIP PACKAGE DEFINITION"""class _SOMEIP_MessageId(Packet):"""MessageId subpacket."""name = 'MessageId'fields_desc = [ShortField('srv_id', 0),BitEnumField('sub_id', 0, 1, {0: 'METHOD_ID', 1: 'EVENT_ID'}),ConditionalField(BitField('method_id', 0, 15), lambda pkt: pkt.sub_id == 0),ConditionalField(BitField('event_id', 0, 15), lambda pkt: pkt.sub_id == 1)]def extract_padding(self, p):return '', pclass _SOMEIP_RequestId(Packet):""" RequestId subpacket."""name = 'RequestId'fields_desc = [ShortField('client_id', 0),ShortField('session_id', 0)]def extract_padding(self, p):return '', pclass SOMEIP(Packet):""" SOME/IP Packet."""# Default valuesPROTOCOL_VERSION = 0x01INTERFACE_VERSION = 0x01# Lenght offset (without payload)LEN_OFFSET = 0x08# SOME/IP TYPE VALUESTYPE_REQUEST = 0x00TYPE_REQUEST_NO_RET = 0x01TYPE_NOTIFICATION = 0x02TYPE_REQUEST_ACK = 0x40TYPE_REQUEST_NORET_ACK = 0x41TYPE_NOTIFICATION_ACK = 0x42TYPE_RESPONSE = 0x80TYPE_ERROR = 0x81TYPE_RESPONSE_ACK = 0xc0TYPE_ERROR_ACK = 0xc1# SOME/IP-TP TYPE VALUESTYPE_REQUEST_SEGMENT = 0x20TYPE_REQUEST_NO_RET_SEGMENT = 0x21TYPE_NOTIFICATION_SEGMENT = 0x22TYPE_REQUEST_ACK_SEGMENT = 0x60TYPE_REQUEST_NORET_ACK_SEGMENT = 0x61TYPE_NOTIFICATION_ACK_SEGMENT = 0x62TYPE_RESPONSE_SEGMENT = 0xa0TYPE_ERROR_SEGMENT = 0xa1TYPE_RESPONSE_ACK_SEGMENT = 0xe0TYPE_ERROR_ACK_SEGMENT = 0xe1SOMEIP_TP_TYPES = frozenset({TYPE_REQUEST_SEGMENT, TYPE_REQUEST_NO_RET_SEGMENT, TYPE_NOTIFICATION_SEGMENT,TYPE_REQUEST_ACK_SEGMENT, TYPE_REQUEST_NORET_ACK_SEGMENT,TYPE_NOTIFICATION_ACK_SEGMENT, TYPE_RESPONSE_SEGMENT, TYPE_ERROR_SEGMENT,TYPE_RESPONSE_ACK_SEGMENT, TYPE_ERROR_ACK_SEGMENT})SOMEIP_TP_TYPE_BIT_MASK = 0x20# SOME/IP RETURN CODESRET_E_OK = 0x00RET_E_NOT_OK = 0x01RET_E_UNKNOWN_SERVICE = 0x02RET_E_UNKNOWN_METHOD = 0x03RET_E_NOT_READY = 0x04RET_E_NOT_REACHABLE = 0x05RET_E_TIMEOUT = 0x06RET_E_WRONG_PROTOCOL_V = 0x07RET_E_WRONG_INTERFACE_V = 0x08RET_E_MALFORMED_MSG = 0x09RET_E_WRONG_MESSAGE_TYPE = 0x0a# SOME/IP-TP More Segments FlagSOMEIP_TP_LAST_SEGMENT = 0SOMEIP_TP_MORE_SEGMENTS = 1_OVERALL_LEN_NOPAYLOAD = 16  # UTname = 'SOME/IP'fields_desc = [PacketField('msg_id', _SOMEIP_MessageId(), _SOMEIP_MessageId),  # MessageIDIntField('len', None),  # LengthPacketField('req_id', _SOMEIP_RequestId(), _SOMEIP_RequestId),  # RequestIDByteField('proto_ver', PROTOCOL_VERSION),  # Protocol versionByteField('iface_ver', INTERFACE_VERSION),  # Interface versionByteEnumField('msg_type', TYPE_REQUEST, {  # -- Message type --TYPE_REQUEST: 'REQUEST',  # 0x00TYPE_REQUEST_NO_RET: 'REQUEST_NO_RETURN',  # 0x01TYPE_NOTIFICATION: 'NOTIFICATION',  # 0x02TYPE_REQUEST_ACK: 'REQUEST_ACK',  # 0x40TYPE_REQUEST_NORET_ACK: 'REQUEST_NO_RETURN_ACK',  # 0x41TYPE_NOTIFICATION_ACK: 'NOTIFICATION_ACK',  # 0x42TYPE_RESPONSE: 'RESPONSE',  # 0x80TYPE_ERROR: 'ERROR',  # 0x81TYPE_RESPONSE_ACK: 'RESPONSE_ACK',  # 0xc0TYPE_ERROR_ACK: 'ERROR_ACK',  # 0xc1}),ByteEnumField('retcode', 0, {  # -- Return code --RET_E_OK: 'E_OK',  # 0x00RET_E_NOT_OK: 'E_NOT_OK',  # 0x01RET_E_UNKNOWN_SERVICE: 'E_UNKNOWN_SERVICE',  # 0x02RET_E_UNKNOWN_METHOD: 'E_UNKNOWN_METHOD',  # 0x03RET_E_NOT_READY: 'E_NOT_READY',  # 0x04RET_E_NOT_REACHABLE: 'E_NOT_REACHABLE',  # 0x05RET_E_TIMEOUT: 'E_TIMEOUT',  # 0x06RET_E_WRONG_PROTOCOL_V: 'E_WRONG_PROTOCOL_VERSION',  # 0x07RET_E_WRONG_INTERFACE_V: 'E_WRONG_INTERFACE_VERSION',  # 0x08RET_E_MALFORMED_MSG: 'E_MALFORMED_MESSAGE',  # 0x09RET_E_WRONG_MESSAGE_TYPE: 'E_WRONG_MESSAGE_TYPE',  # 0x0a}),ConditionalField(BitField('offset', 0, 28), lambda pkt: pkt.msg_type in SOMEIP.SOMEIP_TP_TYPES),ConditionalField(BitField('reserved', 0, 3), lambda pkt: pkt.msg_type in SOMEIP.SOMEIP_TP_TYPES),ConditionalField(BitEnumField('more_segments', 0, 1, {SOMEIP_TP_LAST_SEGMENT: 'Last_Segment',SOMEIP_TP_MORE_SEGMENTS: 'More_Segments'}), lambda pkt: pkt.msg_type in SOMEIP.SOMEIP_TP_TYPES)]def post_build(self, p, pay):length = self.len# length computation : RequestID + PROTOVER_IFACEVER_TYPE_RETCODE + PAYLOADif length is None:length = self.LEN_OFFSET + len(pay)p = p[:4] + struct.pack('!I', length) + p[8:]return p + payfor i in range(15):bind_layers(UDP, SOMEIP, sport=30490 + i)bind_layers(TCP, SOMEIP, sport=30490 + i)

3、network_define.py

class EthParameter:# 广播sd_network_card = "eth0.62"sd_ip = "239.0.0.255"# 本机server_network_card = "Realtek PCIe GbE Family Controller #2"server_ip = "192.168.62.31"# 域控client_network_card = "eth0.62"client_ip = "192.168.62.11"sd_port = 30490producer_port = 30500consumer_prot = 30501

4、packet_define.py

import eth_scapy_sd as sdfrom network_define import EthParameterfrom scapy.layers.l2 import Ether
from scapy.layers.inet import IP, UDPclass SomeipPacker:def __init__(self):self.eth_para = EthParameter()self.sd_session_id = 1self.client_session_id = 1def update_sd_session_id(self):"""更新session_id,从0递增到65535"""self.sd_session_id += 1if self.sd_session_id >= 65535:self.sd_session_id = 0def update_client_session_id(self):self.client_session_id += 1if self.client_session_id >= 65535:self.client_session_id = 0# ===========================##          序列化             ## ===========================#def packet_subscribe(self, service_id: list, ttl: int):"""组事件订阅包"""subscribe_packager = sd.SD()subscribe_packager.setFlag("REBOOT", 1)subscribe_packager.setFlag("UNICAST", 1)subscribe_packager.len_entry_array = 16 * len(service_id)subscribe_packager.len_option_array = 24for i in service_id:subscribe_packager.entry_array.append(sd.SDEntry_EventGroup(type=sd.SDEntry_EventGroup.TYPE_EVTGRP_SUBSCRIBE,srv_id=i,inst_id=0x1,n_opt_1=2,major_ver=0x1,ttl=ttl,eventgroup_id=0x1,))subscribe_packager.option_array = [# UDP EndPointsd.SDOption_IP4_EndPoint(addr=self.eth_para.server_ip,l4_proto=0x11,port=self.eth_para.consumer_prot,),# TCP EndPointsd.SDOption_IP4_EndPoint(addr=self.eth_para.server_ip,l4_proto=0x06,port=self.eth_para.consumer_prot,),]print(subscribe_packager.show())subscribe_package = (Ether()/ IP(src=self.eth_para.server_ip, dst=self.eth_para.client_ip)/ UDP(sport=self.eth_para.sd_port, dport=self.eth_para.sd_port)/ subscribe_packager.get_someip_with_session_id(self.client_session_id, True))self.update_client_session_id()return subscribe_packagedef packet_subscribe_ack(self, service_id: list, ttl: int):"""组ack包"""ack_packager = sd.SD()ack_packager.setFlag("REBOOT", 1)ack_packager.setFlag("UNICAST", 1)ack_packager.len_entry_array = 16 * len(service_id)ack_packager.len_option_array = 0for i in service_id:ack_packager.entry_array.append(sd.SDEntry_EventGroup(type=sd.SDEntry_EventGroup.TYPE_EVTGRP_SUBSCRIBE_ACK,srv_id=i,inst_id=0x1,major_ver=0x1,ttl=ttl,eventgroup_id=0x1,))print(ack_packager.show())ack_package = (Ether()/ IP(src=self.eth_para.server_ip, dst=self.eth_para.client_ip)/ UDP(sport=self.eth_para.sd_port, dport=self.eth_para.sd_port)/ ack_packager.get_someip_with_session_id(self.client_session_id, True))self.update_client_session_id()return ack_packagedef packet_offer(self, service_id: list, ttl: int):"""组offer包"""offer_packager = sd.SD()offer_packager.setFlag("REBOOT", 1)offer_packager.setFlag("UNICAST", 1)offer_packager.len_entry_array = 16 * len(service_id)offer_packager.len_option_array = 12for i in service_id:offer_packager.entry_array.append(sd.SDEntry_Service(type=sd.SDEntry_Service.TYPE_SRV_OFFERSERVICE,srv_id=i,inst_id=0x1,n_opt_1=1,major_ver=0x1,minor_ver=0x01,ttl=ttl,))offer_packager.option_array = [sd.SDOption_IP4_EndPoint(addr=self.eth_para.server_ip,l4_proto=0x11,port=self.eth_para.producer_port,)]print(offer_packager.show())offer_package = (Ether()/ IP(src=self.eth_para.server_ip, dst=self.eth_para.sd_ip)/ UDP(sport=self.eth_para.sd_port, dport=self.eth_para.sd_port)/ offer_packager.get_someip_with_session_id(self.sd_session_id, True))self.update_sd_session_id()return offer_package

5、unpack_define.py

from scapy.packet import Raw
from typing import Optional
from dataclasses import dataclass@dataclass
class SomeIPHeaderParams:service_id: intevent_id: int      # 或 event_id,取决于消息类型client_id: intsession_id: intmsg_type: intreturn_code: intlength: intprotocol_version: intinterface_version: intclass SomeipUnpacker:@staticmethoddef get_someip_header_params(receive_packet) -> Optional[SomeIPHeaderParams]:"""获取someip header"""try:if receive_packet.haslayer("SOME/IP"):someip_layer = receive_packet["SOME/IP"]# 获取someip header值service_id = someip_layer.msg_id.srv_idsub_id = someip_layer.msg_id.sub_idevent_id = sub_id << 15 | someip_layer.msg_id.event_id# 提取请求 ID(Client ID 和 Session ID)req_id = someip_layer.req_idclient_id = req_id.client_idsession_id = req_id.session_id# 构造头部对象return SomeIPHeaderParams(service_id=service_id,event_id=event_id,client_id=client_id,session_id=session_id,msg_type=someip_layer.msg_type,return_code=someip_layer.retcode,length=someip_layer.len,protocol_version=someip_layer.proto_ver,interface_version=someip_layer.iface_ver,)except (AttributeError, ValueError) as e:print(f"解析失败: {e}")return None@staticmethoddef get_someip_payload(receive_packet):"""获取someip payload"""try:someip_payload = receive_packet[Raw].load# print(f"someip_payload: {someip_payload}")return someip_payloadexcept AttributeError:return None

6、someip_controller.py

import threading
import socket
import time
from module.packet_define import *
from module.unpack_define import *from scapy.sendrecv import sendp, sniffclass SomeipController(threading.Thread):def __init__(self, someip_packer: SomeipPacker, someip_unpacker: SomeipUnpacker):super().__init__()self.someip_packer = someip_packerself.someip_unpacker = someip_unpackerself._stop_event = threading.Event()self.running = Truedef run(self):# 创建并发送offer以及订阅ack报文(这里忽略订阅,直接回复订阅ack)service_id_list = [0xA994, 0xA995]_offer = self.someip_packer.packet_offer(service_id=service_id_list,ttl=3)_offer_sender = SomeipSendLoop(network_card=EthParameter.sd_network_card,someip_packer=_offer,cycle_time_ms=2000,)# 回复订阅ACK_subscribe_ack = self.someip_packer.packet_subscribe_ack(service_id=service_id_list,ttl=3)_subscribe_ack_sender = SomeipSendLoop(network_card=EthParameter.sd_network_card,someip_packer=_subscribe_ack,cycle_time_ms=2000,)# 创建并发送订阅报文(订阅0xAB03, 0xAB04)_subscribe = self.someip_packer.packet_subscribe(service_id=[0xAB03,0xAB04], ttl=0x03)_subscribe_sender = SomeipSendLoop(network_card=EthParameter.server_network_card,someip_packer=_subscribe,cycle_time_ms=2000,)# 启动接收模块someip_receiver = SomeIpReceiver(EthParameter.server_network_card)while self._stop_event:time.sleep(0.01)def stop(self):self.running = False  # 设置标志位为 False 来停止self._stop_event.set()self.join()class SomeipSendLoop(threading.Thread):def __init__(self, network_card, someip_packer, cycle_time_ms):super().__init__()self.network_card = network_cardself.someip_packer = someip_packerself.cycle_time_ms = cycle_time_msself._stop_event = threading.Event()self.start()def run(self):while self._stop_event:if self.someip_packer:sendp(self.someip_packer, iface=self.network_card, verbose=0)time.sleep(self.cycle_time_ms / 1000)def stop(self):self._stop_event.set()self.join()class SomeIpReceiver(threading.Thread):def __init__(self,eth_desc: str,someip_unpacker):super().__init__()self._eth_desc = eth_descself._unpacker = someip_unpackerself._terminated = Falseself._stop_event =threading.Event()self.start()def packet_callback(self, packet):if packet:header_param = self._unpacker.get_someip_header_params(receive_packet=packet)print(f"msg_type: {header_param.msg_type}")def run(self):sniff(iface=self._eth_desc,prn=self.packet_callback,filter="udp",stop_filter=lambda x: self._terminated,)while self._stop_event:# 保持sniff嗅探不中断time.sleep(1)def stop(self):self._terminated = Trueself._stop_event.set()self.join()# 如果协议有TCP
class SomeipTcpSocket(threading.Thread):def __init__(self):super().__init__()self.tcp_sock: socket = Noneself._stop_event = threading.Event()self.start()def create_tcp_socket(self):self.tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.tcp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.tcp_sock.bind((EthParameter.icc_android_ip, EthParameter.consumer_prot))self.tcp_sock.connect((EthParameter.adcc_ip, EthParameter.producer_port))def run(self):self.create_tcp_socket()while self._stop_event:# 保持TCP通信time.sleep(1)def stop(self):self._stop_event.set()self.join()if __name__ == "__main__":s_socket = SomeipTcpSocket()s_packer = SomeipPacker()s_unpacker = SomeipUnpacker()sim_server = SomeipController(s_packer,s_unpacker)sim_server.start()

相关文章:

SOME/IP:用Python实现协议订阅、Offer、订阅ACK与报文接收

文章目录 前言一、代码层次二、详细代码1. eth_scapy_sd.py2、eth_scapy_someip.py3、network_define.py4、packet_define.py5、unpack_define.py6、someip_controller.py 前言 1、需要pip安装scapy库 2、需要修改根据实际情况配置network_define.py 3、执行someip_controller…...

嵌入式八股ARM篇

前言 ARM篇主要介绍一下寄存器和中断机制,至于汇编这一块…还请大家感兴趣自行学习 1.寄存器 R0 - R3 R4 - R11 寄存器 R0 - R3一般用作函数传参 R4 - R11用来保存程序运算的中间结果或函数的局部变量 在函数调用过程中 注意在发生异常的时候 cortex-M0架构会自动将R0-R3压入…...

剑指 Offer II 087. 复原 IP

comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20087.%20%E5%A4%8D%E5%8E%9F%20IP/README.md 剑指 Offer II 087. 复原 IP 题目描述 给定一个只包含数字的字符串 s &#xff0c;用以表示一个 IP 地址&#xf…...

RCE-Labs超详细WP-Level10(无字母命令执行_二进制整数替换)

温馨提示 这关涉及的知识点较多, 写的很长, 中间留了很多错误引导(本人在实验时遇到的问题, 或许你们也会遇到), 在后文才逐步解释源码分析 跟前几关一样, 更改了 WAF 的过滤字段这个关卡, 只有0, 1, (单引号), $, <, \ , ( , )可以用解题分析(实验这些命令, 可以先在自己本…...

数据结构(泛型)

1,装箱 int i 10;Integer j Integer.valueOf(i);2.拆箱 Integer i 10;int j i.intValue(); 3.自动装箱 int i 10;Integer j i;int i 10;Integer j (Integer) i; 4,自动拆箱 Integer i 10;int j i;Integer i 10;int j (int) i; 有一段代码需要解析一下&#xff1a; …...

Android Dagger2 框架辅助工具模块深度剖析(六)

一、引言 在 Android 开发领域&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;作为一种至关重要的设计模式&#xff0c;能显著降低代码间的耦合度&#xff0c;提升代码的可测试性与可维护性。Dagger2 作为一款强大的依赖注入框架&#…...

LVGL第三方库的使用(中文库)

一、第三方库文档 3rd party libraries&#xff08;第三方库&#xff09; — LVGL 文档 FreeType 中文字库 SDL 模拟器使用freetype中文字库 1.开启字库 2.安装freetype 字库 sudo apt-get update sudo apt-get install libfreetype6-dev 3.修改makefile 添加字库 4.显示中…...

【愚公系列】《高效使用DeepSeek》009-PPT大纲自动生成

标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度…...

使用easyexcel实现单元格样式设置和下拉框设置

1.单元格样式设置 1.1实体类 public class DemoData {ExcelProperty("PK")private String name;ExcelProperty("年龄")private int age;// 必须提供无参构造方法public DemoData() {}public DemoData(String name, int age) {this.name name;this.age …...

ngx_conf_read_token

file_size ngx_file_size(&cf->conf_file->file.info); 此时 file_size2656 当然还是和上次一样 for ( ;; ) {if (b->pos > b->last) { 此时 b->pos 0x57759a8b77f4 b->last 0x57759a8b8230 b->start0x57759a8b77d0 条件不成立 ch *b->po…...

Certbot实现SSL免费证书自动续签(CentOS 7 + nginx/apache)

在 CentOS 上&#xff0c;你可以使用 Let’s Encrypt 提供的 Certbot 工具来申请和自动续约免费的 SSL 证书。 1. 安装 Certbot CentOS 7 安装 EPEL 和 Certbot yum install -y epel-release yum install -y certbot python3-certbot-nginx如果使用的是 Apache&#xff1a; …...

【使用 Java 调用命令行工具:完整指南】

在 Java 中调用命令行工具是一个常见的需求&#xff0c;尤其是在需要与外部程序交互或执行系统命令时。本文将详细介绍如何使用 Java 调用命令行工具&#xff0c;并提供一个完整的示例来演示如何下载视频。 1. 为什么需要调用命令行工具&#xff1f; 命令行工具通常提供了强大…...

pythonSTL---sys

sys 是 Python 标准库中的一个内置模块&#xff0c;它提供了许多与 Python 解释器和系统环境进行交互的功能。 sys方法 1. 导入 sys 模块 在使用 sys 库的功能之前&#xff0c;需要先导入它&#xff1a; import sys2. 命令行参数 (sys.argv) sys.argv 是一个包含命令行参数…...

数据分布偏移检测:保障模型在生产环境中的稳定性

数据分布偏移检测:保障模型在生产环境中的稳定性 引言 在机器学习系统从开发环境部署到生产环境的过程中,数据分布偏移问题是影响模型性能的主要挑战之一。当训练数据与生产环境中的数据分布不一致时,即使是经过精心调优的模型也可能表现出明显的性能下降。本文将深入探讨…...

redis删除与先判断再删除的区别

在Redis中&#xff0c;“先判断存在再删除”与“直接删除”的区别主要体现在‌操作效率、原子性保障、并发安全性‌三个方面&#xff0c;具体对比如下&#xff1a; ‌1. 操作效率‌ ‌直接删除‌&#xff1a;仅需执行DEL命令一次&#xff0c;无论键是否存在均直接操作&#xf…...

3.6、数字签名

目录 数字签名数字签名与验证过程 数字签名 数字签名是签名者使用自己的私钥对待签名数据的哈希值做密码运算得到的一个结果 第一签名者用自己的私钥来对我们待签数据的哈希值进行签名&#xff0c;直接对数据进行签名其实也是可以的&#xff0c;只是对数据签名&#xff0c;这…...

华为手机助手输入连接码时光标乱跳

问题复现&#xff1a;输入12345678&#xff0c;光标自动跳转导致连接码出现乱序情况。 千万别试着找出规律&#xff0c;已试动态规律非大牛误轻试 问题原因&#xff1a; 想啥呢&#xff1f;华哥的软件又不是我开发我要Know Why干啥 我只需关心解决方案 &#xff08;可能时输入…...

本地化部署Deepseek关于Ollama 安全加固方案(新手易学)

本地化部署Deepseek关于Ollama 安全加固方案&#xff08;新手易学&#xff09; 本方案针对使用ChatBox调用Ollama部署DeepSeek-R1:14b模型时的安全防护需求&#xff0c;提供四重防护措施。 &#x1f512; 一、关闭外网访问&#xff08;关键步骤&#xff09; 1. 修改监听地址 …...

C++ STL算法函数 —— 应用及其操作实现

一、STL算法函数分类概述 STL算法库提供了大量实用函数&#xff0c;按功能可分为以下五类&#xff1a; 1. 不修改序列的操作 定义&#xff1a;这些算法不会改变容器中的元素&#xff0c;仅对数据进行查询或统计。 典型函数&#xff1a; 函数功能示例find(first, last, value…...

AI数字人:口播与唇形同步的福音,支持本地部署/批量生成/口齿清晰

Heygem&#xff1a;开源前端界面的老六玩家 好消息&#xff01;Heygem 在 GitHub 上开源了&#xff01;不过&#xff0c;嘻嘻&#xff0c;只是前端界面开源&#xff0c;感觉更像是来 GitHub 刷一波知名度。不过这依然是个值得关注的工具。让我们先来看看它的官方介绍&#xff…...

【鸿蒙】封装日志工具类 ohos.hilog打印日志

封装一个ohos.hilog打印日志 首先要了解hilog四大日志类型&#xff1a; info、debug、warm、error 方法中四个参数的作用 domain: number tag: string format: string ...args: any[ ] 实例&#xff1a; //普通的info日志&#xff0c;使用info方法来打印 //第一个参数 : 0x0…...

附下载 | 2024 OWASP Top 10 基础设施安全风险.pdf

《2024 OWASP Top 10 基础设施安全风险》报告&#xff0c;由OWASP&#xff08;开放网络应用安全项目&#xff09;发布&#xff0c;旨在提升企业和组织对基础设施安全风险、威胁与漏洞的意识&#xff0c;并提供高质量的信息和最佳实践建议。报告列出了2024年最重要的10大基础设施…...

Chatbox通过百炼调用DeepSeek

解决方案链接&#xff1a;评测&#xff5c;零门槛&#xff0c;即刻拥有DeepSeek-R1满血版 方案概览 本方案以 DeepSeek-R1 满血版为例进行演示&#xff0c;通过百炼模型服务进行 DeepSeek 开源模型调用&#xff0c;可以根据实际需求选择其他参数规模的 DeepSeek 模型。百炼平台…...

Vue前端项目部署到宝塔面板的详细过程

目录 前言 一、项目的打包与上传 1、修改前端项目 2、关于test环境的补充修改 3、打包前端项目 二、添加站点&#xff0c;启动项目 三、总结 前言 书接上回 SpringBoot项目部署到宝塔面板的详细过程-CSDN博客 本次以SmartAdmin的项目为例&#xff0c;通过宝塔面板部署…...

免费高质量贴图(Textures) 网站推荐

以下是一些提供 免费或高质量贴图&#xff08;Textures&#xff09; 的网站&#xff0c;包括 PBR 贴图、HDRI 贴图、材质等&#xff0c;适用于 Three.js、Blender、Unity、Unreal Engine 等软件。 &#x1f30d; 1. Poly Haven&#xff08;https://polyhaven.com/&#xff09;⭐…...

C++进阶——map和set的使用

目录 1、序列式容器和关联式容器 2、set系列的使用 2.1 set和multiset的参考文档 2.2 set类的介绍 2.3 set的构造和迭代器 2.4 set的增删查 2.5 set的insert和迭代器遍历 2.6 set的find和erase 2.7 set的lower_bound和upper_bound 2.8 multiset和set的差异 2.9 349.…...

AI机器学习---Anaconda

Anaconda指的是一个开源的Python发行版本&#xff0c;其包含了Conda、Python等180多个科学包及其依赖项。因为包含了大量的科学包&#xff0c;Anaconda 的下载文件比较大&#xff08;约 531 MB&#xff09;&#xff0c;如果只需要某些包&#xff0c;或者需要节省带宽或存储空间…...

如何在Futter开发中做性能优化?

目录 1. 避免不必要的Widget重建 问题&#xff1a;频繁调用setState()导致整个Widget树重建。 优化策略&#xff1a; 2. 高效处理长列表 问题&#xff1a;ListView一次性加载所有子项导致内存暴涨。 优化策略&#xff1a; 3. 图片加载优化 问题&#xff1a;加载高分辨率…...

leetcode 75.颜色分类(荷兰国旗问题)

题目描述 题目分析 本题是经典的「荷兰国旗问题」&#xff0c;由计算机科学家 Edsger W. Dijkstra 首先提出。 要想单独解决这道题本身还是很简单的&#xff0c;统计0、1、2的数量然后按顺序赋值&#xff0c;或者手写一个冒泡排序&#xff0c;whatever。 但是在这一题中我们主…...

JVM--垃圾回收

垃圾回收的概念 垃圾回收主要针对的是堆中的对象&#xff0c;堆是一个共享的区域&#xff0c;创建的对象和数组都放在这个位置。但是我们不能一直的创建对象&#xff0c;也不是所有的对象能一直存放&#xff0c;如果不进行垃圾回收&#xff0c;内存迟早会耗尽&#xff0c;及时…...

Spring boot3-Http Interface: 声明式编程

来吧 1.首先引入pom.xml依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId> </dependency> 2.创建WebClientController控制器 import com.atguigu.boot3_07_http.serv…...

springboot EasyExcel 实现导入导出

1. 添加依赖 确保 Maven 依赖中包含 EasyExcel 3.0.5&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version></dependency><!-- excel工具 --><dep…...

基于RAGFlow本地部署DeepSpeek-R1大模型与知识库:从配置到应用的全流程解析

作者&#xff1a;后端小肥肠 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; DeepSpeek服务器繁忙&#xff1f;这几种替代方案帮你流畅使用&#xff01;&#xff08;附本地部署教程&#xff09;-CSDN博客 10分钟上手…...

spring 创建单例 Bean 源码分析

一、创建单例Bean 1、创建单例 Bean 通过方法getBean()来创建单例bean。 代码入口&#xff1a; org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons spring boot version&#xff1a;2.6.13 org.springframework.beans.factory…...

GetCurrentTime

在实际编程中难免要获取当前时间并且进行格式化&#xff0c;本文给出了多种 GetCurrentTime() 方法以供选择。 C语言下使用strftime C 语言中可以使用 <time.h> 中的函数来获取和格式化时间 #include <stdio.h> #include <time.h>char* getCurrentTime() …...

HTB 学习笔记 【中/英】《Web 应用简介》P1

&#x1f4cc; 这篇文章讲了什么&#xff1f; 介绍了 Web 应用 的概念、架构&#xff0c;以及与传统网站的区别。重点讲解了 Web 安全风险&#xff0c;包括 常见攻击方法&#xff08;SQL 注入、文件包含、不安全的文件上传等&#xff09;。介绍了 Web 渗透测试 的重要性&#…...

ROS catkin_make编译报错问题

对问题 CMake Error at graduation_design/CMakeLists.txt:226 (add_dependencies): The dependency target "graduation_design_generate_messages_cpp" of target "listener" does not exist 检查 generate_messages() 是否被注释 对 CMake Error at …...

【结构设计】3D打印创想三维Ender 3 v2

【结构设计】3D打印创想三维Ender 3 v2 文章目录 前言一、Creality Slicer1.2.3打印参数设置二、配件更换1.捆扎绑扎线2.气动接头3D打印机配件插头3.3D打印机配件Ender3pro/V2喷头套件4.读卡器 TF卡5.micro sd卡 三、调平四、参考文章总结 前言 使用工具&#xff1a; 1.创想三…...

并发编程2

接并发编程1 synchronized锁的实现 通过底层指令控制实现&#xff0c;Java提供的一种原子性内置锁&#xff0c;在进入synchronized后会从主内存复制一份共享变量到自己的工作内存&#xff0c;在工作内存中修改完成后&#xff0c;退出时会把工作内存的值写入到主内存&#xff…...

Linux 中 Git 使用指南:从零开始掌握版本控制

目录 1. 什么是 Git&#xff1f; Git 的核心功能&#xff1a; 2. Git 的安装 Ubuntu/Debian 系统&#xff1a; 验证安装&#xff1a; 3.gitee库 4. Git 的首次配置 配置用户名和邮箱&#xff1a; 查看配置&#xff1a; 5. Git 的基本使用 初始化仓库 添加文件到暂存区…...

C# Exe + Web 自动化 (BitComet 绿灯 自动化配置、设置)

BitComet GreenLight,内网黄灯转绿灯 (HighID), 增加p2p连接率提速下载-CSDN博客 前两天写个这个&#xff0c;每次开机关机后要重来一遍很麻烦的索性写个自动化。 先还是按照上面的教程自己制作一遍&#xff0c;留下Luck 以及 路由器相关的 端口记录信息。 &#xff08;因为自…...

2024年12月CCF-GESP编程能力等级认证C++编程四级真题解析

四级真题的难度: 一、总体难度评价 CCF-GESP编程能力等级认证C++四级真题的难度通常被认为相对较高。它不仅要求考生具备扎实的C++编程基础,还需要考生掌握一定的算法和数据结构知识,以及良好的问题解决能力。 二、具体难度分析 ‌理论知识考察‌: 单选题和判断题中,会涉…...

谷歌Chrome或微软Edge浏览器修改网页任意内容

在谷歌或微软浏览器按F12&#xff0c;打开开发者工具&#xff0c;切换到console选项卡&#xff1a; 在下面的输入行输入下面的命令回车&#xff1a; document.body.contentEditable"true"效果如下&#xff1a;...

《DeepSeek深度使用教程:开启智能交互新体验》Deepseek深度使用教程

《DeepSeek使用教程&#xff1a;开启智能交互新体验》 在当今数字化时代&#xff0c;人工智能技术正以前所未有的速度改变着我们的生活和工作方式。DeepSeek作为一款强大的人工智能工具&#xff0c;凭借其卓越的自然语言处理能力和多领域应用潜力&#xff0c;受到了众多开发者…...

Dijkstra算法

Dijkstra算法&#xff08;迪杰斯特拉算法&#xff09;是一种经典的单源最短路径算法&#xff0c;用于在加权图中找到从一个源点到所有其他顶点的最短路径。它要求图中不能有负权边&#xff0c;因为负权边可能会导致算法的贪心策略失效。 Dijkstra算法的基本思想 Dijkstra算法…...

Python中的静态方法如何使用?

在Python里&#xff0c;类当中的方法可以分为多种不同的类型&#xff0c;其中staticmethod是一个十分有趣而又实用的功能。我们来好好地聊一聊什么是静态方法&#xff0c;它的用途是什么&#xff0c;以及如何在实际应用中使用它们&#xff01; 首先&#xff0c;定义一下静态方…...

【最后203篇系列】016 Q201架构思考

前言 Q200已经达到了我既定的目标&#xff0c;在最近的3个月&#xff0c;我需要进一步完善&#xff0c;达到可以试产的程度。 在这个过程当中&#xff0c;许多知识和体会一直在变。 qtv200到目前&#xff0c;虽然通过习惯(每晚运行离线策略和比对)方式维持了注意力的集中&…...

小脑萎缩会致命吗?

小脑萎缩&#xff0c;顾名思义&#xff0c;是指小脑的体积减小或结构发生异常&#xff0c;进而影响其正常功能。小脑作为人体重要的协调和运动控制中心&#xff0c;负责维持身体平衡、调节肌肉张力和协调运动等关键功能。当小脑出现萎缩时&#xff0c;患者可能会出现步态不稳、…...

pip install和conda install的区别

这里写目录标题 一、什么是 Python 依赖&#xff08;Python Dependencies&#xff09;&#xff1f;1. 依赖的作用2. 如何管理 Python 依赖3. 依赖管理问题4. 依赖锁定总结 二、使用pip安装包venv隔离环境方法 1&#xff1a;使用 venv&#xff08;推荐&#xff09;创建虚拟环境激…...

這是我第一次寫關於aapenal服務器管理控制面板的文章

首先我們來認識一下服務器管理面板的所有功能  網站管理功能&#xff1a; 支持創建和管理多個網站。配置虛擬主機&#xff08;Vhost&#xff09;和域名綁定。自動安裝常用應用&#xff08;如WordPress、Joomla等&#xff09;。  文件管理功能&#xff1a; 文件上傳、…...