lpcmanager

901a6f6fa6ec
Parents 9e7923ad7ae3
Children efa336421340
Fix issues with multiple clients blocking PLC communications
--- a/iec60870/iec60870_runtime.c Mon May 04 07:42:48 2026 +0200
+++ b/iec60870/iec60870_runtime.c Wed May 06 13:37:40 2026 +0200
@@ -82,30 +82,30 @@
*(IEC_BOOL *)p = v ? (IEC_BOOL)1 : (IEC_BOOL)0;
}
-/* CP24/CP56 helpers for time-tagged ASDUs (wall-clock; PLC does not drive protocol timestamps). */
-static struct sCP56Time2a iec_wall_cp56_storage;
-static struct sCP24Time2a iec_zero_cp24_storage;
-static struct sCP16Time2a iec_zero_cp16_storage;
-
-static CP56Time2a iec_wall_cp56(void) {
- CP56Time2a t = (CP56Time2a)&iec_wall_cp56_storage;
+/* CP24/CP56 helpers for time-tagged ASDUs (wall-clock; PLC does not drive protocol timestamps).
+ * Buffers are caller-provided so concurrent iec_make_monitor_io() calls do not share static state. */
+static CP56Time2a iec_wall_cp56_buf(struct sCP56Time2a *buf) {
+ CP56Time2a t = (CP56Time2a)buf;
CP56Time2a_createFromMsTimestamp(t, (uint64_t)time(NULL) * 1000ULL);
return t;
}
-static CP24Time2a iec_zero_cp24(void) {
- memset(&iec_zero_cp24_storage, 0, sizeof(iec_zero_cp24_storage));
- return (CP24Time2a)&iec_zero_cp24_storage;
+static CP24Time2a iec_zero_cp24_buf(struct sCP24Time2a *buf) {
+ memset(buf, 0, sizeof(*buf));
+ return (CP24Time2a)buf;
}
-static CP16Time2a iec_zero_cp16(void) {
- memset(&iec_zero_cp16_storage, 0, sizeof(iec_zero_cp16_storage));
- return (CP16Time2a)&iec_zero_cp16_storage;
+static CP16Time2a iec_zero_cp16_buf(struct sCP16Time2a *buf) {
+ memset(buf, 0, sizeof(*buf));
+ return (CP16Time2a)buf;
}
static InformationObject iec_make_monitor_io(int type_id, int ioa, struct iec60870_binding *b) {
void *mem = malloc((size_t)InformationObject_getMaxSizeInMemory());
QualityDescriptor q = IEC60870_QUALITY_GOOD;
+ struct sCP56Time2a cp56_wall;
+ struct sCP24Time2a cp24_z;
+ struct sCP16Time2a cp16_z;
struct sBinaryCounterReading bcr_st;
BinaryCounterReading bcr;
uint32_t pack_u32;
@@ -128,11 +128,11 @@
case M_SP_TA_1:
return (InformationObject)SinglePointWithCP24Time2a_create(
(SinglePointWithCP24Time2a)mem, ioa, iec_get_bool_var(b->iec_var), q,
- iec_zero_cp24());
+ iec_zero_cp24_buf(&cp24_z));
case M_SP_TB_1:
return (InformationObject)SinglePointWithCP56Time2a_create(
(SinglePointWithCP56Time2a)mem, ioa, iec_get_bool_var(b->iec_var), q,
- iec_wall_cp56());
+ iec_wall_cp56_buf(&cp56_wall));
case M_DP_NA_1: {
DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
@@ -142,12 +142,12 @@
case M_DP_TA_1: {
DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
return (InformationObject)DoublePointWithCP24Time2a_create(
- (DoublePointWithCP24Time2a)mem, ioa, dv, q, iec_zero_cp24());
+ (DoublePointWithCP24Time2a)mem, ioa, dv, q, iec_zero_cp24_buf(&cp24_z));
}
case M_DP_TB_1: {
DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
return (InformationObject)DoublePointWithCP56Time2a_create(
- (DoublePointWithCP56Time2a)mem, ioa, dv, q, iec_wall_cp56());
+ (DoublePointWithCP56Time2a)mem, ioa, dv, q, iec_wall_cp56_buf(&cp56_wall));
}
case M_ST_NA_1:
@@ -156,11 +156,11 @@
case M_ST_TA_1:
return (InformationObject)StepPositionWithCP24Time2a_create(
(StepPositionWithCP24Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q,
- iec_zero_cp24());
+ iec_zero_cp24_buf(&cp24_z));
case M_ST_TB_1:
return (InformationObject)StepPositionWithCP56Time2a_create(
(StepPositionWithCP56Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q,
- iec_wall_cp56());
+ iec_wall_cp56_buf(&cp56_wall));
case M_BO_NA_1:
return (InformationObject)BitString32_createEx(
@@ -168,11 +168,11 @@
case M_BO_TA_1:
return (InformationObject)Bitstring32WithCP24Time2a_createEx(
(Bitstring32WithCP24Time2a)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q,
- iec_zero_cp24());
+ iec_zero_cp24_buf(&cp24_z));
case M_BO_TB_1:
return (InformationObject)Bitstring32WithCP56Time2a_createEx(
(Bitstring32WithCP56Time2a)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q,
- iec_wall_cp56());
+ iec_wall_cp56_buf(&cp56_wall));
case M_ME_NA_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
@@ -182,12 +182,12 @@
case M_ME_TA_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
return (InformationObject)MeasuredValueNormalizedWithCP24Time2a_create(
- (MeasuredValueNormalizedWithCP24Time2a)mem, ioa, nv, q, iec_zero_cp24());
+ (MeasuredValueNormalizedWithCP24Time2a)mem, ioa, nv, q, iec_zero_cp24_buf(&cp24_z));
}
case M_ME_TD_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
return (InformationObject)MeasuredValueNormalizedWithCP56Time2a_create(
- (MeasuredValueNormalizedWithCP56Time2a)mem, ioa, nv, q, iec_wall_cp56());
+ (MeasuredValueNormalizedWithCP56Time2a)mem, ioa, nv, q, iec_wall_cp56_buf(&cp56_wall));
}
case M_ME_ND_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
@@ -201,11 +201,11 @@
case M_ME_TB_1:
return (InformationObject)MeasuredValueScaledWithCP24Time2a_create(
(MeasuredValueScaledWithCP24Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, q,
- iec_zero_cp24());
+ iec_zero_cp24_buf(&cp24_z));
case M_ME_TE_1:
return (InformationObject)MeasuredValueScaledWithCP56Time2a_create(
(MeasuredValueScaledWithCP56Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, q,
- iec_wall_cp56());
+ iec_wall_cp56_buf(&cp56_wall));
case M_ME_NC_1:
return (InformationObject)MeasuredValueShort_create(
@@ -213,11 +213,11 @@
case M_ME_TC_1:
return (InformationObject)MeasuredValueShortWithCP24Time2a_create(
(MeasuredValueShortWithCP24Time2a)mem, ioa, *(IEC_REAL *)b->iec_var, q,
- iec_zero_cp24());
+ iec_zero_cp24_buf(&cp24_z));
case M_ME_TF_1:
return (InformationObject)MeasuredValueShortWithCP56Time2a_create(
(MeasuredValueShortWithCP56Time2a)mem, ioa, *(IEC_REAL *)b->iec_var, q,
- iec_wall_cp56());
+ iec_wall_cp56_buf(&cp56_wall));
case M_IT_NA_1:
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
@@ -227,12 +227,12 @@
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
(int32_t)(*(UDINT *)b->iec_var), 0, false, false, false);
return (InformationObject)IntegratedTotalsWithCP24Time2a_create(
- (IntegratedTotalsWithCP24Time2a)mem, ioa, bcr, iec_zero_cp24());
+ (IntegratedTotalsWithCP24Time2a)mem, ioa, bcr, iec_zero_cp24_buf(&cp24_z));
case M_IT_TB_1:
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
(int32_t)(*(UDINT *)b->iec_var), 0, false, false, false);
return (InformationObject)IntegratedTotalsWithCP56Time2a_create(
- (IntegratedTotalsWithCP56Time2a)mem, ioa, bcr, iec_wall_cp56());
+ (IntegratedTotalsWithCP56Time2a)mem, ioa, bcr, iec_wall_cp56_buf(&cp56_wall));
case M_EP_TA_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
@@ -241,20 +241,20 @@
SingleEvent_setEventState(se, (EventState)(pack_u32 & 3u));
SingleEvent_setQDP(se, (QualityDescriptorP)((pack_u32 >> 2) & 0xffu));
return (InformationObject)EventOfProtectionEquipment_create(
- (EventOfProtectionEquipment)mem, ioa, se, iec_zero_cp16(), iec_zero_cp24());
+ (EventOfProtectionEquipment)mem, ioa, se, iec_zero_cp16_buf(&cp16_z), iec_zero_cp24_buf(&cp24_z));
case M_EP_TB_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
ste = (StartEvent)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedStartEventsOfProtectionEquipment_create(
- (PackedStartEventsOfProtectionEquipment)mem, ioa, ste, qdp, iec_zero_cp16(),
- iec_zero_cp24());
+ (PackedStartEventsOfProtectionEquipment)mem, ioa, ste, qdp, iec_zero_cp16_buf(&cp16_z),
+ iec_zero_cp24_buf(&cp24_z));
case M_EP_TC_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
oci = (OutputCircuitInfo)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedOutputCircuitInfo_create(
- (PackedOutputCircuitInfo)mem, ioa, oci, qdp, iec_zero_cp16(), iec_zero_cp24());
+ (PackedOutputCircuitInfo)mem, ioa, oci, qdp, iec_zero_cp16_buf(&cp16_z), iec_zero_cp24_buf(&cp24_z));
case M_EP_TD_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
@@ -263,22 +263,22 @@
SingleEvent_setEventState(se, (EventState)(pack_u32 & 3u));
SingleEvent_setQDP(se, (QualityDescriptorP)((pack_u32 >> 2) & 0xffu));
return (InformationObject)EventOfProtectionEquipmentWithCP56Time2a_create(
- (EventOfProtectionEquipmentWithCP56Time2a)mem, ioa, se, iec_zero_cp16(),
- iec_wall_cp56());
+ (EventOfProtectionEquipmentWithCP56Time2a)mem, ioa, se, iec_zero_cp16_buf(&cp16_z),
+ iec_wall_cp56_buf(&cp56_wall));
case M_EP_TE_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
ste = (StartEvent)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedStartEventsOfProtectionEquipmentWithCP56Time2a_create(
(PackedStartEventsOfProtectionEquipmentWithCP56Time2a)mem, ioa, ste, qdp,
- iec_zero_cp16(), iec_wall_cp56());
+ iec_zero_cp16_buf(&cp16_z), iec_wall_cp56_buf(&cp56_wall));
case M_EP_TF_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
oci = (OutputCircuitInfo)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedOutputCircuitInfoWithCP56Time2a_create(
- (PackedOutputCircuitInfoWithCP56Time2a)mem, ioa, oci, qdp, iec_zero_cp16(),
- iec_wall_cp56());
+ (PackedOutputCircuitInfoWithCP56Time2a)mem, ioa, oci, qdp, iec_zero_cp16_buf(&cp16_z),
+ iec_wall_cp56_buf(&cp56_wall));
case M_PS_NA_1:
memset(&scd_st, 0, sizeof(scd_st));
@@ -316,7 +316,7 @@
(FileSegment)mem, ioa, 0, 0, &seg_dummy, 0);
case F_DR_TA_1:
return (InformationObject)FileDirectory_create(
- (FileDirectory)mem, ioa, 0, (uint32_t)*(UDINT *)b->iec_var, 0, iec_wall_cp56());
+ (FileDirectory)mem, ioa, 0, (uint32_t)*(UDINT *)b->iec_var, 0, iec_wall_cp56_buf(&cp56_wall));
default:
free(mem);
@@ -584,8 +584,9 @@
if (srv->conn_bool) {
if (event == CS104_CON_EVENT_ACTIVATED)
iec_set_bool_var(srv->conn_bool, true);
- else if (event == CS104_CON_EVENT_DEACTIVATED || event == CS104_CON_EVENT_CONNECTION_CLOSED)
- iec_set_bool_var(srv->conn_bool, CS104_Slave_getOpenConnections(srv->slave) > 0);
+ /* Do not call CS104_Slave_getOpenConnections here — it re-enters the slave from inside
+ * lib60870 connection callbacks and can deadlock with multiple clients. conn_bool is
+ * refreshed every PLC cycle in __retrieve_*(). */
}
}
@@ -605,6 +606,10 @@
CS104_Slave_setLocalAddress(s->slave, s->ip_str);
CS104_Slave_setLocalPort(s->slave, s->port);
CS104_Slave_setMaxOpenConnections(s->slave, s->max_open);
+ /* Each master TCP session is an independent redundancy group. The default SINGLE_REDUNDANCY_GROUP
+ * shares one app-layer context across all peers; closing one connection can break others
+ * (e.g. two GiMonitor clients). */
+ CS104_Slave_setServerMode(s->slave, CS104_MODE_CONNECTION_IS_REDUNDANCY_GROUP);
{
CS101_AppLayerParameters al = CS104_Slave_getAppLayerParameters(s->slave);