Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to add CommandItem? #6

Open
kenghuaku opened this issue Jul 24, 2023 · 29 comments
Open

How to add CommandItem? #6

kenghuaku opened this issue Jul 24, 2023 · 29 comments
Assignees
Labels
question Further information is requested

Comments

@kenghuaku
Copy link

In my case,
sending get attribute single will get will get "incorrect data" RR,
I compared this with other Python projects, and the packets sent out from them were successful. The differences between the two are shown in the two pictures below. In this case, there is one less CommandItemNull in the CommandItem. Could you guide me on how to add and test this?

image
image

@gisellevonbingen gisellevonbingen self-assigned this Jul 24, 2023
@gisellevonbingen gisellevonbingen added the question Further information is requested label Jul 24, 2023
@kenghuaku
Copy link
Author

Perhaps the problem might lie in the incorrect calculation of length. As shown in the picture below, the correct value should be 24.
image

@gisellevonbingen
Copy link
Owner

That length 20 is probably right.
Because length should not include command id and length self.
Command Id (0x006F): 2 Bytes
Length: 2 Bytes
So, length be 20 subtracted 4 bytes from total data 24 bytes.

I pushed a new commit e5d42d5
It commit mean you can add another CommandItem before exchange SendRRData.

public DataProcessor GetAttribute(Stream stream, AttributePath path) => this.ExchangeSendRRData(stream,
this.CIPCodec.HandleGetAttribute,
this.CIPCodec.CreateGetAttribute(path));

Try upper code like to below and test.

public DataProcessor GetAttribute(Stream stream, AttributePath path) => this.ExchangeSendRRData(stream,
	this.CIPCodec.HandleGetAttribute, // This is response callback
	// Array of CommandItem for send
   new CommandItemNull(), this.CIPCodec.CreateGetAttribute(path));

I'm not currently in a situation where I can test, sorry.

@gisellevonbingen
Copy link
Owner

else.. can you send screenshot showing length when success?

@kenghuaku
Copy link
Author

image

Success! Additionally, the link to the program I used for packet comparison is as follows:
https://github.com/xl7dev/ICSecurity/blob/master/ethernetip.py

@kenghuaku
Copy link
Author

And now continue debug,Forward Open is failed, error package:
image

the OK package:
image

@kenghuaku
Copy link
Author

The image provided above is incorrect; it's the one for successful return. The correct one (python successful transmission) is shown below.
image

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 24, 2023

Fixed an issue where ForwardOpen's SendRRData wasn't passing the Originator's UDP port number right now. 45eca2e
Pull and retry please.

@kenghuaku
Copy link
Author

public ForwardOpenResult ForwardOpen(Stream stream, ForwardOpenOptions options) => this.ExchangeSendRRData(stream,
response => this.CIPCodec.HandleForwardOpen(response, options),
new CommandItemNull(), this.CIPCodec.CreateForwardOpen(options));
-> cannot add new CommandItemNull() after new commit, and the CommandItemNull is necessary.

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 24, 2023

Oops, i forgot it.

In this code,

public IEnumerable<CommandItem> CreateForwardOpen(ForwardOpenOptions options)
{
var udRequest = new CommandItemUnconnectedDataRequest();
udRequest.ServiceCode = ServiceCode.ForwardOpen;
udRequest.Path = new EPath()
{
EPathSegmentLogical.FromClassID(KnownClassID.ConnectionManager),
EPathSegmentLogical.FromInstanceID(0x01)
};

Try edit like this.

public IEnumerable<CommandItem> CreateForwardOpen(ForwardOpenOptions options)
{
	// Add this line
	yield return new CommandItemNull();

	var udRequest = new CommandItemUnconnectedDataRequest();

@kenghuaku
Copy link
Author

image
Same error, CS1660, cannot convert Lambda to CommandItem

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 24, 2023

'yield return new CommandItemNull();' is will instead of it.
Remove 'new CommandItemNull(), ' in picture

@kenghuaku
Copy link
Author

Currently everything is OK, but in my case, ConfigData must be added to ForwardOpen. Below is the Python implementation code:

path = struct.pack(">I",0x34040000) + struct.pack("I",0) +
struct.pack(">I",0x00002004) + struct.pack("B",0x24) + struct.pack("B",configinst) +
struct.pack("B",0x2c) + struct.pack("B",outputinst) + struct.pack("B",0x2c) +
struct.pack("B",inputinst)
plen = int(len(path) / 2)
if configData != None:
# 0x80 = simple data segment, with length in words => max 512 bytes of data
if len(configData) > 512:
return 1
path += struct.pack("BB", 0x80, int(len(configData)/2) )
path += configData
plen = int(len(path) / 2)

how should I correspondingly add it to project?

P.S. The Configuration Assembly ID should be set in this location of the CreateForwardOpen method:
.....
var connectionPath = new EPath
{
EPathSegmentLogical.FromClassID(options.ClassID),
EPathSegmentLogical.FromInstanceID(0x66) // Configuration Assembly ID is 0x66
};
......

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 24, 2023

Is it work?????? AWESOME.
I know now python code created what data, but I don't understand why it works.
Can you take a screenshot that from wireshark?

@kenghuaku
Copy link
Author

My BALLUFF EtherNet/IP Scanner for IO-Link (referred to by BALLUFF as EtherNet/IP IO-LINK Master by EIP) needs to go through a configuration process. Only after this, the TO/OT UDP data will carry information, otherwise, its content is empty. However, other communications are currently functioning normally. If I first connect it to a PLC (which has an EDS that can be viewed), and then go through the PLC's configuration process, the data can be normally obtained on the C# program side. I plan to capture packets on the PLC equipment, but I currently don't have a port mirroring switch, so you'll need to wait for me for a bit.

@kenghuaku
Copy link
Author

image

I want to add the following configData to connectionPath. How can I do that? (How to modify EPathSegmentLogical.cs?)"

int Configuration_Length = 194;
byte[] configData = new byte[Configuration_Length + 2]; // 2: first: 0x80 for Simple Data Segment, second: word length, 2 bytes = 1 words
configData[0] = 0x80;
configData[1] = (byte)(Configuration_Length / 2);
configData[2] = 0xff;
configData[3] = 0xff;

@gisellevonbingen
Copy link
Owner

The InstanceID of the Assembly you use for ForwardOpen is 0x64, 0x65?

@kenghuaku
Copy link
Author

yes, just like following code:

// Reserved
reqProcessor.WriteByte(0x30);
reqProcessor.WriteByte(0x30);
reqProcessor.WriteByte(0x30);

        reqProcessor.WriteUInt(options.O_T_Assembly.RequestPacketRate);
        reqProcessor.WriteUShort(options.O_T_Assembly.Flags);
        reqProcessor.WriteUInt(options.T_O_Assembly.RequestPacketRate);
        reqProcessor.WriteUShort(options.T_O_Assembly.Flags);

        // Transport Type/Trigger
        reqProcessor.WriteByte(0x01);

        var connectionPath = new EPath
        {
            EPathSegmentLogical.FromClassID(options.ClassID),
            EPathSegmentLogical.FromInstanceID(0x66)    // kenghua: Configuration Assembly ID
        };

        if (options.O_T_Assembly.ConnectionType != ConnectionType.Null)
        {
            connectionPath.Add(EPathSegmentLogical.FromConnectionPointID(options.O_T_Assembly.InstanceID));
        }

        if (options.T_O_Assembly.ConnectionType != ConnectionType.Null)
        {
            connectionPath.Add(EPathSegmentLogical.FromConnectionPointID(options.T_O_Assembly.InstanceID));
        }

image

And all setting and data will define in EDS that PLC will be automatically configured, so there is no need for manual setup like we do.

@kenghuaku
Copy link
Author

I extended EPathSegmentLogical.cs by adding SimpleDataSegment = 0x80, and handling the value as byte[] (I added byte[] ValueByteArray). I also modified the WriteValue method to write byte[] values. Everything went well, and I successfully configured my EIP Scanner through the Configuration Assembly. Although the specification mentioned this part, it was quite vague, and few devices use this for configuration (usually, Explicit Messaging is used for configuration). In any case, I'm extremely grateful to gisellevonbingen; you are truly amazing bro!
image

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 25, 2023

Already complete? Wow.. you genius..
You've figured out exactly where to change it.

I been had push a new commit 818c125 just right now.
The code changes were tested on my device(ControlTechniques Drive), and I confirmed that compatibility was kept.
You just need to add the code as below.

var connectionPath = new EPath
{
	EPathSegmentLogical.FromClassID(options.ClassID),
	EPathSegmentLogical.FromInstanceID(0x01)
};

if (options.O_T_Assembly.ConnectionType != ConnectionType.Null)
{
	connectionPath.Add(EPathSegmentLogical.FromConnectionPointID(options.O_T_Assembly.InstanceID));
}

if (options.T_O_Assembly.ConnectionType != ConnectionType.Null)
{
	connectionPath.Add(EPathSegmentLogical.FromConnectionPointID(options.T_O_Assembly.InstanceID));
}

// Add this line
connectionPath.Add(new EPathSegmentData(KnownEPathDataSegmentType.Simple, new byte[] { your config data }));

connectionPath.Write(reqProcessor);

@kenghuaku
Copy link
Author

"Simple Data Segment" in Connection Path type is 0x80

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 25, 2023

Yes it is, in byte.
image
in this picture, upper 3 bits mean segment type. => 0x04 (0b100)
and remaining 5 bits are data type. => 0x00 (0b00000)
They can be merge to 0x80. (0b10000000)

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 25, 2023

Wait.. i saw a typo...

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 25, 2023

Now i think it should work. c72f731
image

@kenghuaku
Copy link
Author

please fix bug:
public void WriteValue(DataProcessor processor) method:
processor.WriteByte((byte)payload.Length); ---->
processor.WriteByte((byte)(payload.Length / 2)); // kenghua: the first byte represents the data length, calculated in words. 2bytes = 1word.

AND...
Do you have any suggestions for establishing connections with multiple machines? (All connections cannot bind same local port 2222)

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 25, 2023

How length does pass when length is odd?
ceiling? flooring?
I decided to 'ceil' for now. 7a83555

I have plan for port change.
I'll test it a bit more and let you know.

@gisellevonbingen
Copy link
Owner

gisellevonbingen commented Jul 26, 2023

Can change PC port with T_O_UDPPort 319a02c

public static ForwardOpenResult ForwardOpen(ENIPSimpleClient client)
{
var openOptions = new ForwardOpenOptions();
openOptions.T_O_UDPPort = 2222; // Support alternate port, Default is 2222
// T : Target
// O : Originator
openOptions.O_T_Assembly.InstanceID = 101; // Your O->T assembly instance id
openOptions.O_T_Assembly.Length = 64;
openOptions.O_T_Assembly.RealTimeFormat = RealTimeFormat.Header32Bit;
openOptions.O_T_Assembly.ConnectionType = ConnectionType.PointToPoint;
openOptions.O_T_Assembly.RequestPacketRate = 50000; // 50,000 ns = 50 ms;
openOptions.T_O_Assembly.InstanceID = 100; // Your T->O assembly instance id
openOptions.T_O_Assembly.Length = 64;
openOptions.T_O_Assembly.RealTimeFormat = RealTimeFormat.Modeless;
openOptions.T_O_Assembly.ConnectionType = ConnectionType.PointToPoint;
openOptions.T_O_Assembly.RequestPacketRate = 50000; // 50,000 ns = 50 ms;
return client.ForwardOpen(openOptions);

When change O_T_UDPPort to 1234

var openOptions = new ForwardOpenOptions();
openOptions.T_O_UDPPort = 1234; // Support alternate port, Default is 2222

image
image

@kenghuaku
Copy link
Author

A few questions are listed below for discussion, and I suggest you can commit back to the main branch, thank you:

  1. This is the correct way, payload.Length don't need add 1, and the specifications require providing the length in Word, so there should not be definitions with an odd number of byte lengths:
    public void WriteValue(DataProcessor processor)
    {
    var payload = this.Payload;
    processor.WriteByte((byte)(payload.Length / 2));
    processor.WriteBytes(payload);
    // Pad to words
    processor.WriteBytes(new byte[payload.Length % 2]);
    }

  2. In ENIPCodec.cs, I suggest adding CommandItemNull according to our previous discussion, because when I capture PLC communication packets, they do the same. Could you test the compatibility of doing this on your equipment? If I don't add it on my side, the device will directly indicate a format error and refuse to OpenForward:
    public DataProcessor GetAttribute(Stream stream, AttributePath path) => this.ExchangeSendRRData(stream,
    this.CIPCodec.HandleGetAttribute,
    new CommandItemNull(), this.CIPCodec.CreateGetAttribute(path));

     public byte SetAttribute(Stream stream, AttributePath path, byte[] values) => this.ExchangeSendRRData(stream,
         this.CIPCodec.HandleSetAttribute,
         new CommandItemNull(), this.CIPCodec.CreateSetAttribute(path, values));
    
     public ForwardOpenResult ForwardOpen(Stream stream, ForwardOpenOptions options) => this.ExchangeSendRRData(stream,
         response => this.CIPCodec.HandleForwardOpen(response, options),
         this.CIPCodec.CreateForwardOpen(options));
    
     public ForwardCloseResult ForwardClose(Stream stream, ForwardCloseOptions options) => this.ExchangeSendRRData(stream,
         response => this.CIPCodec.HandleForwardClose(response, options),
         new CommandItemNull(), this.CIPCodec.CreateForwardClose(options));
    
  3. The issue with IPV6 exists on .net core, and I have already upgraded to use .net core. Along the line-map of Microsoft's .Net, I also suggest you do the same. I noticed that you have specifically implemented IPV4 enforcement for communication, but there are still some places that have been missed, leading to the capture of IPV6 internal IP addresses. The following areas need to be modified (I apologize, I kind of forgot what changes I've made), just like:

options.LocalAddress = ((IPEndPoint)this.TcpClient.Client.LocalEndPoint).Address; -> options.LocalAddress = ((IPEndPoint)this.TcpClient.Client.LocalEndPoint).Address.MapToIPv4();

search all "TcpClient(" and "UDPClient{",then add: new TcpClient(AddressFamily.InterNetwork) and new UdpClient(AddressFamily.InterNetwork)

@gisellevonbingen
Copy link
Owner

  1. This is to prevent users from accidentally entering odd-length arrays.
    If sure to simple data's data type is word, It might be better to declare it as ushort[] instead of byte[].

  2. OK i will add it if it works on my test.

  3. my device doesn't support IPv6, so maybe i can't test it.
    umm.. i will do that for now.

@gisellevonbingen
Copy link
Owner

  1. For users, byte[] will be more convenient, so I hold off for now.

  2. Looks good work on my device, It added from 940f8dd

  3. I think can use IPv6 at PC internally.
    So... added map LocalEndPoint to IPv4 just before IPAddress be passed to device in here. 8c4232c

    if (options.T_O_Assembly.ConnectionType == ConnectionType.Multicast)
    {
    toEndPoint.EndPoint.Address = ((int)GetMulticastAddress((uint)options.LocalAddress.ToIPv4Address())).ToIPv4Address(true);
    }
    else
    {
    toEndPoint.EndPoint.Address = IPAddress.Any;
    }
    yield return toEndPoint;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants