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

Method call without input or output arguments is not showing expected behaviour #34

Closed
BernhardH2 opened this issue May 30, 2024 · 10 comments

Comments

@BernhardH2
Copy link
Contributor

Hi Andrew,

As the final step in push certificate management on a GDS server, I am calling the ApplyChanges method from a client application currently being set up using Go. Unfortunately, nothing really happens, whereas the method call using UaExpert or a python implementation yields the expected results. As the certificate writing, signing and updating otherwise works perfrectly fine using Go, I'm unsure where to start looking.

A selection of the trace looks as follows:

2024/05/30 15:49:18 CallRequest{ "AuthenticationToken": "i=3041481013", "Timestamp": "2024-05-30T15:49:18.8335444+02:00", "RequestHandle": 5, "ReturnDiagnostics": 0, "AuditEntryID": "", "TimeoutHint": 15000, "AdditionalHeader": null, "MethodsToCall": [ { "ObjectID": "i=12637", "MethodID": "i=12740", "InputArguments": null } ] } 2024/05/30 15:49:18 CallResponse{ "Timestamp": "2024-05-30T13:49:25.9168499Z", "RequestHandle": 5, "ServiceResult": 0, "ServiceDiagnostics": {}, "StringTable": null, "AdditionalHeader": null, "Results": [ { "StatusCode": 0, "InputArgumentResults": null, "InputArgumentDiagnosticInfos": null, "OutputArguments": null } ], "DiagnosticInfos": null }

The method call is set up as:

req := &ua.CallRequest{ MethodsToCall: []ua.CallMethodRequest{ { ObjectID: ua.ObjectIDServerConfiguration, MethodID: ua.MethodIDServerConfigurationApplyChanges, }, }, }

It doesn't really seem to matter whether InputArguments is nil, an empty ua.Variant slice, or omitted altogether.

Would you have any suggestion on where to start looking, or on what other information to share?

@awcullen
Copy link
Owner

awcullen commented Jun 1, 2024

Thanks for question.
The InputArguments need to be an empty array to be encoded correctly.

func ExampleClient_Call() {

	ctx := context.Background()

	// open a connection to testserver running locally. Testserver is started if not already running.
	ch, err := client.Dial(
		ctx,
		"opc.tcp://localhost:48010",
		client.WithClientCertificatePaths("./pki/client.crt", "./pki/client.key"), // need secure channel to send password
		client.WithUserNameIdentity("root", "secret"),                             // need role of "operator" to call this method
		client.WithInsecureSkipVerify(),                                           // skips verification of server certificate
	)
	if err != nil {
		fmt.Printf("Error opening client connection. %s\n", err.Error())
		return
	}

	// prepare call request
	req := &ua.CallRequest{
		MethodsToCall: []ua.CallMethodRequest{
			{
				ObjectID:       ua.ObjectIDServerConfiguration,
				MethodID:       ua.MethodIDServerConfigurationApplyChanges,
				InputArguments: []ua.Variant{},
			},
		},
	}

	// send request to server. receive response or error
	res, err := ch.Call(ctx, req)
	if err != nil {
		fmt.Printf("Error calling method. %s\n", err.Error())
		ch.Abort(ctx)
		return
	}

	// print results
	fmt.Printf("Call method result:\n")
	fmt.Println(res.Results[0].StatusCode)

	// close connection
	err = ch.Close(ctx)
	if err != nil {
		ch.Abort(ctx)
		return
	}

	// Output:
	// Call method result:
	// The operation completed successfully.
}

@BernhardH2
Copy link
Contributor Author

Hi Andrew,

Many thanks for the quick reply. Although I'm fairly certain I tried it that way already, I'll double check on Monday and will post the results here.

@BernhardH2
Copy link
Contributor Author

Hi Andrew,

Unfortunately, the example does not yield any different results from what I've seen before. Let me try to illustrate what's currently happening and what is expected.

Current situation:
The Siemens PLC is in provisioning mode and all method calls to open, write, close and update the trustlists works as expected. As does creating a singing request and consequently updating the certificates. Only ApplyChanges is not showing the expected behaviour as seen in TIA Portal:
image
which overlaps with the trace of the method call:

2024/06/03 08:18:33 CallRequest{
 "AuthenticationToken": "i=2470488320",
 "Timestamp": "2024-06-03T08:18:33.9970046+02:00",
 "RequestHandle": 5,
 "ReturnDiagnostics": 0,
 "AuditEntryID": "",
 "TimeoutHint": 15000,
 "AdditionalHeader": null,
 "MethodsToCall": [
  {
   "ObjectID": "i=12637",
   "MethodID": "i=12740",
   "InputArguments": []
  }
 ]
}
2024/06/03 08:18:34 CallResponse{
 "Timestamp": "2024-06-03T06:18:38.8290046Z",
 "RequestHandle": 5,
 "ServiceResult": 0,
 "ServiceDiagnostics": {},
 "StringTable": null,
 "AdditionalHeader": null,
 "Results": [
  {
   "StatusCode": 0,
   "InputArgumentResults": null,
   "InputArgumentDiagnosticInfos": null,
   "OutputArguments": null
  }
 ],
 "DiagnosticInfos": null
}

However, when the method call is executed through UA Expert (or python for that matter), the method call is actually executed and the Diagnostics buffer indicates that provisioning has been successful:
image

So even though the operation is completed successfully, something seems to be going wrong.

Again, any help would be greatly appreciated and if I can provide any additional information to better understand the problem I would be happy to do so.

@awcullen
Copy link
Owner

awcullen commented Jun 5, 2024

Is it possible you are connecting with different security mode or user identity?

Also, I have a S7-1500. I don't see the ServerConfiguration node. I'll have to do some reading :(

@BernhardH2
Copy link
Contributor Author

Hi Andrew,

I've just created a pull request with some GDS push example code. A connected PLC should be in provisioning mode, which can be achieved after following section 2.3 in this Siemens guide:
https://cache.industry.siemens.com/dl/files/888/109799888/att_1082229/v4/109799888_OPCUA_GDSPush_DOC_V1.0_en.pdf

The endpointURL and UserNameIdentitiy should probably be modified in cmd/testGDSPush/main.go, but all required certificates should generate automatically.

A hint on where the issue might originate from is that the method call returns BadInvalidState when executed from the example code. However, this should not be the case. The fact that the method can be successfully called from UA Expert and Python after OpenWriteCloseAndUpdate() demonstrates that the function call should execute successfully using Go too.

Let me know if this is of any help!

@JessevGool
Copy link

Hey @awcullen have you had a chance to look into the issue since the last comment?
I have since then tested the method calls using the gopcua library.
The implementation used by that library works without any problems.
Hopefully their implementation might give you some insight on what is causing the issue

@awcullen
Copy link
Owner

awcullen commented Jul 4, 2024

I moved the TestGDSPush code to a branch 'GDSPush'. I tested with UnifiedAutomation's UACPPServer.
The program

  1. creates ca cert.
  2. creates client cert (signed by ca cert).
  3. connects to server.
  4. receives certificate signing request from server.
  5. creates new server cert (signed by ca cert).
  6. updates server with new server cert (and apply changes).
  7. updates server with new trust list (ca cert and ca crl).

Note:
I updated the library to accept certificate chains where it was expecting a single cert. (A chain being leaf, intermediate and root certs combined in one byte array)

Initially the server would not accept the client connection until I added the ca cert to its trust list manually.

I don't believe we need to perform Step 7 if the ca cert doesn't change.

@BernhardH2
Copy link
Contributor Author

Hi Andrew,
Many thanks for your work. If anything, it has definitely improved the code. I've been testing your changes with a Siemens 1510SP F-1PN PLC today but I'm sorry to say step 6 still doesn't work. Updating the trust list works perfectly fine and the trustlist certificates are visible in UAExpert's GDS Push View, but calling ApplyChanges after the CSR still seems to do absolutely nothing, while a manual ApplyChanges method call from UAExpert does the job. I might try to decrypt the messages sent from Go and UAExpert later on to see if that provides any additional information, but this will be for a later time.
In any case, thanks again. I'll let you know if I find any more information.

@awcullen
Copy link
Owner

I got to test with a Siemens 1500 and found the encoder prefers arrays (slices) of zero length (instead of nil). I have updated branch 'GDSPush'. Let me know if it works for you and I will update main branch.

@BernhardH2
Copy link
Contributor Author

Hi Andrew, that did the trick! Thanks for checking with the Siemens PLC and directly providing the solution.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants